1,概念:
进程:一个正在执行的程序,他是资源分配的最小单位。进程中的事情语言按照一定顺序逐一进行
线程:又称轻量级线程,程序执行的最小单位,系统独立调度和分派CPU的基本单位,他是进程中一个实体,一个进程中可以有多个线程,这些线程共享进程的所有资 源,线程本身只包含一点必不可少的资源。
并发:指在同一时刻,只能有执行一条指令,但多个线程指令被快速轮换执行,使得宏观上具有多个进程同时执行的效果 。 (看起来同时发生,针对单核)
并行:指在同一时刻,有多条指令在多个处理器上同时执行。 (真正的同时发生)
同步:彼此有依赖关系的调用不应该“同时发生”,而同步就是阻止那些“同时发生”的事情
异步:与同步相对,任何两个彼此独立的操作时异步的,它表明事情独立发生
多线程的优势:
1)在多处理器中开发程序的并行性
2)在等待慢速IO操作时,程序可以执行其他操作,提高并发性
3)模块化的编程,能更清晰的表达程序中独立事件的关系,结构清晰
4)占用较少的系统资源
多线程不一定要多处理器
线程创造 | 获取ID,生命周期 |
线程控制 | 终止、连接、取消、发送信号、清除操作 |
线程同步 | 互斥量、读写锁、条件变量 |
线程高级控制 | 一次性初始化、线程属性、同步属性、私有数据、安全的fork() |
2,线程的基本控制
创建新线程
线程ID
线程 | 进程 | |
标识符类型 | pthread_t | pid_t |
获取ID | pthread_seif() | getpid() |
创建 | pthread_create() | fork() |
pthread_t : 结构体 unsigned long int (linux 中提高移植性) /usr/include/bits/pthreadtypes.h
获取线程ID : pthread_self()
头文件: #include <pthread.h>
函数: pthread_t pthread_seif();
返回值 : 线程ID
编译链接时需要用到线程库 -pthread ::gcc -pthread (用到线程都要用)
创建线程:ptnread_create()
函数: int ptnread_create ( ptnread_t *restrict tidp, const ptnread_attr_t *restrict attr , void *(*start_routine)(void *) , void *restrict arg)
参数: 第一个参数: 新线程的ID,如果成功则新线程的ID回填充到tipe 指向的内存
第二个参数 : 线程属性 (调整策略,继承性,分离性...)
第三个参数 : 回调函数 (新线程要执行的函数)
第四个参数 : 回调函数的参数
返回值 : 成功 0 ; 失败则返回错误码
编译时需要连接库 -pthread 主线程结束后必须有延时让其创建新线程,不然主进程直接被返回。方法用延时或者 下述2
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <string.h> 7 void print_id(char *s) 8 { 9 pid_t pid; 10 pthread_t tid; 11 12 pid = getpid(); 13 tid = pthread_self(); 14 15 printf("%s pid is %u,the tid is %lu ",s,pid,tid); 16 17 } 18 19 void *thread_fun (void *arg) 20 { 21 print_id(arg); 22 return (void *)0; 23 } 24 25 int main() 26 { 27 pthread_t ntid; 28 int err; 29 30 err = pthread_create (&ntid, NULL , thread_fun ,"new thread"); 31 32 if(err!=0) 33 { 34 printf("create new thread failure "); 35 return 0; 36 } 37 // printf("create new thread sucess "); 38 print_id("main thread:"); 39 sleep(2); 40 return 0; 41 } 42 ~ 43 44 结果: 45 main thread: pid is 2833,the tid is 3076491008 46 new thread pid is 2833,the tid is 3076488000
线程的生命周期
1,当C程序运行时,首先运行main()函数;在线程代码中,这个特殊的执行流被称为初始线程或者主线程。可以在初始线程中做任何普通线程可以做的事情
2,主线程的特殊性在于,它在main() 函数返回的时候,会导致进程的结束,进程内所有的线程也将会结束。为了避免这种情况,需要在主线程中调用 pthread_exit() 函数,这样进程就会等所有线程结束时才终止。
3,主线程接收参数的方式是通过argc 和argv[] ;而普通线程只有一个参数 void*
4,绝大多数情况下,主线程在默认堆栈上运行,这个堆栈可以 延长,而普通线程的堆栈会收到限制,一旦溢出就会出错
5,主线程是随着进程的创建而创建,其他线程可以通过调用函数来创建 ,主要为 pthread_create();
6,新线程可能在当前线程从函数 pthread_cteate () 返回之前就已经开始运行,甚至可能在返回之前就已经运行完毕
1 #include "stdio.h" 2 #include "pthread.h" 3 #include "stdlib.h" 4 #include "unistd.h" 5 #include "sys/types.h" 6 #include "string.h" 7 8 struct student 9 { 10 int age; 11 char name[20]; 12 char id[5]; 13 }; 14 15 void *thread(void *s) 16 { 17 18 printf("student age is %d,name is %s,id is %s ",((struct student *)s)->age,((struct student *)s)->name,((struct student *)s)->id); 19 return (void *)0; 20 21 } 22 23 24 int main(int argc,char *argv[]) 25 { 26 27 pthread_t tid; 28 int err; 29 30 31 struct student stu; 32 stu.age =20; 33 memcpy(stu.name , "zhangsan" , 20); 34 memcpy(stu.id , "0001" , 5); 35 err = pthread_create(&tid , NULL , thread , (void *)(&stu)); 36 if(err != 0) 37 { 38 39 printf("creat new thread failure "); 40 return 0; 41 42 } 43 int i; 44 for(i=0;i<argc;i++) 45 { 46 printf("main thread args is %s ",argv[i]); 47 48 } 49 sleep(1); 50 return 0; 51 52 } 53 ~ 54 结果: 55 root@ubuntu:/home/xiaozhao# gcc -pthread b.c -o b 56 root@ubuntu:/home/xiaozhao# ./b 21 32 42 57 main thread args is ./b 58 main thread args is 21 59 main thread args is 32 60 main thread args is 42 61 student age is 20,name is zhangsan,id is 0001
1 #include "stdio.h" 2 #include "pthread.h" 3 #include "stdlib.h" 4 #include "unistd.h" 5 #include "sys/types.h" 6 #include "string.h" 7 8 void *thread(void *s) 9 { 10 int i=0; 11 while(1) 12 { 13 if(i%2 ==1) 14 15 printf("new thread is %d ",i); 16 i++; 17 sleep(1); 18 } 19 return (void *)0; 20 21 } 22 23 24 int main(int argc,char *argv[]) 25 { 26 27 pthread_t tid; 28 int err; 29 30 err = pthread_create(&tid , NULL , thread , "new thread"); 31 if(err != 0) 32 { 33 34 printf("creat new thread failure "); 35 return 0; 36 } 37 int i=0; 38 while(i<10) 39 { 40 if(i%2 == 0) 41 printf( "main thread is %d ",i); 42 i++; 43 sleep(1); 44 } 45 return 0; 46 47 } 48 49 50 结果: 51 root@ubuntu:/home/xiaozhao# ./c 52 main thread is 0 53 new thread is 1 54 main thread is 2 55 new thread is 3 56 main thread is 4 57 new thread is 5 58 main thread is 6 59 new thread is 7 60 main thread is 8 61 new thread is 9 62 63 64 65 66 注意: 67 主线程循环结束,新线程也被结束;而新线程循环结束,不会影响主线程 68
线程的四个状态
就绪: 当线程刚被创建处于就绪状态,或者当线程解除阻塞以后也会处于就绪状态。就绪的线程在等待一个可用的处理器,当一个运行的线程被抢占时,它立即又回到就绪状态
运行 :线程正在运行,在多核系统中,可能同时又多个线程在运行
阻塞: 线程在等待处理器意外的其他条件(试图加锁一个已经被锁住的互斥量,等待某个条件变量,调用singwait 等待尚未发生的信号,执行无法完成的IO信号,由于内存错误)
终止: 线程从启动函数中返回,或者调用 pthread_exit() 函数,或者被取消
回收
线程的分离属性:
分离一个正在运行的线程并不影响它,仅仅是通知当前系统该线程结束的时,其所属的资源可以回收,一个没有被分离的线程在终止时会保留其虚拟内存,包括他们的堆栈和其他系统资源,有时这种线程被称为“僵尸进程”。创建线程时默认时非分离的。
如果线程具有分离属性,线程终止时会被立刻回收,回收将释放掉所有在线程终止时未被释放的系统资源和进程资源,包括保存线程返回值的内存空间、堆栈、保存寄存器的内存空间等
终止被分离的线程会释放所有的系统资源,但是必须释放有该线程占有的程序资源。由malloc() 或 mmap() 分配的内存可以在任何时候由任何线程释放,条件变量、互斥量、信号灯可以由任何线程销毁,只要他们被解锁了或者没有线程等待,但是只有互斥量的主人才能解锁它,所以在线程终止前,需要解锁互斥量
*********************************************************************************************************************************
线程终止
1, 如果进程中的任意一个线程调用了exit() , _Exit ,_exit , 那么整个进程就会终止
2,不终止进程的退出方式:
普通的单个线程有以下三种方式退出,不会终止进程:
- 从启动历程中返回,返回值是线程的退出码 return
- 线程可以被同一进程中的其他线程取消
- 线程调用pthread_exit(void *rval) 函数,rval 是退出码
1 #include "stdio.h" 2 #include "pthread.h" 3 #include "stdlib.h" 4 #include "unistd.h" 5 #include "sys/types.h" 6 #include "string.h" 7 8 void *thread_fun (void *s) 9 { 10 if(strcmp("1",(char *)s)==0) 11 return (void *)1; 12 13 if(strcmp("2",(char *)s)==0) 14 pthread_exit((void *)2); 15 16 if(strcmp("3",(char *)s)==0) 17 exit(3); 18 } 19 20 21 int main(int argc, char *argv[]) 22 { 23 pthread_t tid; 24 int err; 25 26 err = pthread_create (&tid , NULL , thread_fun , (void *)argv[1]); 27 if (err !=0) 28 { 29 printf("create new thread failure "); 30 return 0; 31 } 32 sleep(1); 33 printf("my is main thread "); 34 return 0; 35 36 } 37 结果: 38 root@ubuntu:/home/xiaozhao# ./d 1 39 my is main thread 40 root@ubuntu:/home/xiaozhao# ./d 2 41 my is main thread 42 root@ubuntu:/home/xiaozhao# ./d 3 43 root@ubuntu:/home/xiaozhao# 44 45 46 说明:exit()会直接导致进程退出,而前两种不会
线程连接(一个进程等待另一个进程完成在结束)
int pthread_join (pthread_t tid , void **rval)
- 调用该函数的线程会一直阻塞,直到指定的线程tid 调用 pthread_exit () ; 从启动历程返回或者被取消
- 参数tid 就是指定线程的ID
- 参数ravl 是指定线程的返回码,如果线程被取消,那么ravl 被置为 PTHREAD_CANCELED
- 该函数调用成功会返回0,失败返回错误码
调用pthread_join 会使指定的线程处于分离状态,如果指定线程已经处于分离状态,那么调用就会失败
pthread_detach () 可以分离一个线程,线程可以自己分离自己
int pthread_datach (pthread_t thread);
成功返回0 ; 失败返回错误码
1 #include "apb.h" 2 3 void *thread_fun1 (void *s) 4 { 5 printf("my is new thread1 "); 6 return (void *)1; 7 8 } 9 10 void *thread_fun2 (void *s) 11 { 12 13 printf("my is new thread2 "); 14 pthread_detach(pthread_self()); 15 pthread_exit((void *)2); 16 } 17 18 int main() 19 { 20 int err1,err2; 21 pthread_t tid1,tid2; 22 void *rval1,*rval2; 23 24 25 err1 = pthread_create (&tid1 , NULL , thread_fun1 , NULL ); 26 sleep(1); 27 err2 = pthread_create (&tid2 , NULL , thread_fun2 , NULL ); 28 sleep(1); 29 if( err1 || err2 ) 30 { 31 printf("create new thread failure "); 32 return 0; 33 } 34 35 printf("my is main thread "); 36 printf("join rval1 is %d ",pthread_join(tid1,&rval1)); 37 printf("join rval2 is %d ",pthread_join(tid2,&rval2)); 38 39 printf("thread1 exit is %d ",(int )rval1); 40 printf("thread2 exit is %d ",(int )rval2); 41 42 43 return 0; 44 } 45 ~ 46 47 结果: 48 my is new thread1 49 my is new thread2 50 my is main thread 51 join rval1 is 0 52 join rval2 is 22 53 thread1 exit is 1 54 thread2 exit is -1216983040 55 56 问题: 57 如果创建之后不加延时,线程二会比线程一早完成,输出早。。目前不知原因及解决办法 58 my is main thread 59 my is new thread2 60 my is new thread1 61 join rval1 is 0 62 join rval2 is 22 63 thread1 exit is 1 64 thread2 exit is -1216983040
线程取消
取消函数
int pthread_cancel (pthread_t tid);
取消tid指定的线程,成功返回0.但是取消只是发送一个请求,并不意味着等待线程终止,而且发送成功也不意味着tid一定会终止。
取消状态
取消状态就是线程对取消信号的处理方式,忽略或者响应。线程创建时默认响应取消信号
int pthread_setcancelstate (int state , int *oldstate);
设置本线程对cancel 信号的反应,state有两种值 : PTHREAD_CANCEL_ENABLE(缺省)和PTHREAD_CANCEL_DISABLE。。分别表示收到信号后设为CANCLED状态和忽略CANCEL信号 继续运行;old_state 如果不为NULL ,则存入原来的CANCEL 状态以便恢复
取消类型
取消类型时线程对取消信号的响应方式,立即取消或者延时取消。线程创建时默认为延时取消
int pthread_setcanceltype (int type , int *oldtype);
设置本线程取消动作的执行时机,type由两种取值: PTHREAD_CANCEL_DEFFERED 和 PTHREAD_CANCEL_ASYCHRONOUS , 仅当cancel 状态 为ENABLE时有效,分别表示收到信号后继续运行至下一个取消点再退出,和 立即执行取消动作 (退出); oldtype 如果不为NULL ,则存入运来的取消动作类型值。
取消点
取消一个线程,他通常需要被取消线程的配合。线程在很多时候会查看自己是否由取消请求。如果有,就主动退出,这些查看是否有取消的地方成为取消点
很多地方都是包含取消点,包括 pthread_join() , pthread_teacancel() , 等等大多数会阻塞的系统调用
1 线程被取消 2 #include "apb.h" 3 4 void *thread_fun (void *s) 5 { 6 int stateval; 7 stateval = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE , NULL); 8 if (stateval != 0) 9 { 10 printf("set cancel state failure "); 11 } 12 printf("my is new thread "); 13 sleep(4); 14 15 printf("about to cancel "); 16 stateval = pthread_setcancelstate (PTHREAD_CANCEL_ENABLE , NULL); 17 printf("first cancel point "); 18 printf("secong cancel point "); 19 return (void *)20; 20 21 22 } 23 24 25 26 int main() 27 { 28 pthread_t tid; 29 int err,cval; 30 int jval; 31 void *rval; 32 33 err = pthread_create (&tid , NULL , thread_fun , NULL); 34 if (err != 0) 35 { 36 printf("create new thread failure "); 37 return 0; 38 } 39 sleep(2); 40 cval = pthread_cancel(tid); 41 if(cval !=0) 42 { 43 printf("cancel thread failure "); 44 } 45 jval = pthread_join(tid, &rval); 46 47 printf("cancel rval is %d ",(int )rval); 48 return 0; 49 50 51 } 52 53 结果: 54 my is new thread 55 about to cancel 56 first cancel point 57 cancel rval is -1 58 59 60 线程忽略取消信号 61 62 #include "apb.h" 63 64 void *thread_fun (void *s) 65 { 66 int stateval; 67 stateval = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE , NULL); 68 if (stateval != 0) 69 { 70 printf("set cancel state failure "); 71 } 72 printf("my is new thread "); 73 sleep(4); 74 75 printf("about to cancel "); 76 stateval = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE , NULL); 77 printf("first cancel point "); 78 printf("secong cancel point "); 79 return (void *)20; 80 81 82 } 83 84 85 86 int main() 87 { 88 pthread_t tid; 89 int err,cval; 90 int jval; 91 void *rval; 92 93 err = pthread_create (&tid , NULL , thread_fun , NULL); 94 if (err != 0) 95 { 96 printf("create new thread failure "); 97 return 0; 98 } 99 sleep(2); 100 cval = pthread_cancel(tid); 101 if(cval !=0) 102 { 103 printf("cancel thread failure "); 104 } 105 jval = pthread_join(tid, &rval); 106 107 printf("cancel rval is %d ",(int )rval); 108 return 0; 109 110 111 } 112 结果: 113 114 my is new thread 115 about to cancel 116 first cancel point 117 secong cancel point 118 cancel rval is 20 119 120 121 线程接收到取消信号立即结束: 122 #include "apb.h" 123 124 void *thread_fun (void *s) 125 { 126 int stateval,type; 127 stateval = pthread_setcancelstate (PTHREAD_CANCEL_DISABLE , NULL); 128 if (stateval != 0) 129 { 130 printf("set cancel state failure "); 131 } 132 printf("my is new thread "); 133 sleep(4); 134 135 printf("about to cancel "); 136 stateval = pthread_setcancelstate (PTHREAD_CANCEL_ENABLE , NULL); 137 type = pthread_setcanceltype (PTHREAD_CANCEL_ASYNCHRONOUS, NULL); 138 printf("first cancel point "); 139 printf("secong cancel point "); 140 return (void *)20; 141 142 143 } 144 145 146 147 int main() 148 { 149 pthread_t tid; 150 int err,cval; 151 int jval; 152 void *rval; 153 154 err = pthread_create (&tid , NULL , thread_fun , NULL); 155 if (err != 0) 156 { 157 printf("create new thread failure "); 158 return 0; 159 } 160 sleep(2); 161 cval = pthread_cancel(tid); 162 if(cval !=0) 163 { 164 printf("cancel thread failure "); 165 } 166 jval = pthread_join(tid, &rval); 167 168 printf("cancel rval is %d ",(int )rval); 169 return 0; 170 171 172 } 173 结果: 174 175 my is new thread 176 about to cancel 177 cancel rval is -1 178
向线程发送信号
int pthread_kill ( pthread_t thread , int sig );
向指定ID的线程发送SIG信号,如果线程代码内不做处理,则按照信号默认的行为影响整个进程。(也就就是说,如果给一个线程发送SIGQUIT,但线程没有实现signal处理函数,则整个进程退出);
如果int sig 是0 , 为保留信号,其实并没有发送信号,只是用来判断线程是不是还活着
成功返回0;失败返回出错码;
线程的信号:
进程信号的处理:
int sigaction ( int signum ,const struct sigaction *act , struct sigaction *oldact);
给信号signum设置一个处理函数,函数在sigaction 中指定
{
atc.sa_mask 信号屏蔽字
act.as_handler 信号集处理程序
}
int sigemptyset (sigset_t *set); 清空信号集
int sigfillset (sigset_t *set); 将所有信号加入信号集
int sigaddset (sigset_t *set , int signum); 增加一个信号到信号集
int sigdelset (sigset_t *set , int signum); 删除一个信号到信号集
多线程信号屏蔽处理
int pthread_sigmask (int how , const sigset_t *set , sigset_t *oidset);
how = SIG_BLOCK : 向当前信号掩码中添加set ,其中set 表示要阻塞的信号组
SIG_UNBLOCK : 向当前信号掩码中删除set ,其中set 表示取消阻塞的信号组
SIG_SETMASK : 将当前信号掩码替换为set ,其中set 表示新的信号掩码。
在多线程中,新线程的当前信号掩码会继承创造它的那个线程的信号掩码
一般情况下,被阻塞的信号将不能中断词线程的执行,除非词信号的产生是因为程序出错
SIGSEGV : 另外不能被忽略处理的信号 SIGKILL 和SIGSTOP 也无法被阻塞
线程清除操作:
线程可以安排它退出时的清除操作,这与进程的可以用 atexit() 函数安排进程退出时需要调用的函数类似。这样的函数称为线程清理程序。线程可以建立多个清除程序,处理程序记录在栈中,所以这些清理程序执行的顺序与他们注册程序的顺序相反
pthread_cleanup_push ( void (*trn) (void *) , void *args) //注册清理程序
pthread_cleanup_pop (int excute) // 清除清理程序
这两个必须成对出现,否则编译无法通过
当执行以下操作时,调用清理函数,清理函数的参数由args传入
- 调用pthread_exit();
- 响应取消请求
- 用非零参数来调用pthread_cleanup_pop
1 1 #include "apb.h" 2 2 3 3 void *first_clean(void *s) 4 4 { 5 5 printf("%s first clean ",(char *)s); 6 6 return (void *)0; 7 7 } 8 8 9 9 void *second_clean(void *s) 10 10 { 11 11 printf("%s second clean ",(char *)s); 12 12 return (void *)0; 13 13 } 14 14 15 15 void *thread_fun1 (void *s) 16 16 { 17 17 printf("new thread1 "); 18 18 pthread_cleanup_push(first_clean,"thread1"); 19 19 pthread_cleanup_push(second_clean,"thread1"); 20 20 21 21 pthread_cleanup_pop(1); 22 22 pthread_cleanup_pop(0); 23 23 24 24 return (void *)1; 25 25 26 26 } 27 27 28 28 void *thread_fun2 (void *s) 29 29 { 30 30 printf("new thread2 "); 31 31 pthread_cleanup_push(first_clean,"thread2"); 32 32 pthread_cleanup_push(second_clean,"thread2"); 33 33 34 34 pthread_cleanup_pop(1); 35 35 pthread_cleanup_pop(1); 36 36 37 37 return (void *)1; 38 38 39 39 } 40 40 41 41 42 42 int main() 43 43 { 44 44 pthread_t tid1,tid2; 45 45 int err; 46 46 void *rval1,*rval2; 47 47 err = pthread_create (&tid1 , NULL , thread_fun1 , NULL); 48 48 if(err != 0) 49 49 { 50 50 printf("create new thread1 failure "); 51 51 return; 52 52 53 53 } 54 54 55 55 err = pthread_create (&tid2 , NULL , thread_fun2 , NULL); 56 56 if(err != 0) 57 57 { 58 58 printf("create new thread2 failure "); 59 59 return; 60 60 61 61 } 62 sleep(1); 63 65 return 0; 64 66 65 67 66 68 67 69 } 68 ~ 69 结果: 70 71 new thread2 72 thread2 second clean 73 thread2 first clean 74 new thread1 75 thread1 second clean
*线程的同步
互斥量
当多个线程共享相同的内存时,需要每一个线程看到相同的视图,当一个线程修改变量时,而其他线程也可以读取或者修改这个变量,就需要对这些线程进行同步,确保它们不会访问到无效的变量。
互斥量的初始化和销毁:
为了让线程访问数据不产生冲突,这就需要对变量加锁,使得同一时刻只有一个线程可以访问变量。互斥量本身就是锁,访问资源前对互斥量加锁,访问完成后解锁。
当互斥量加锁后,其他所有需要访问该互斥量的线程都将阻塞
当互斥量加锁以后,所有因为这个互斥量阻塞的线程都将变为就绪状态,第一个获得cpu 的线程会获得互斥量,变为运行态。而其他线程继续阻塞,在这种访问方式下,互斥量每次只有一个线程能向前执行
互斥量用pthread_mutex_t 类型的数据表示,在使用前需要对互斥量初始化
- 1,如果是动态分配的互斥量,可以调用 pthread_mutex_init() 函数初始化
- 2,如果是静态非配的互斥量,还可以把他置为常量PTHREAD_MUTEX_INITIALIZER
- 3,动态分配的互斥量在释放内存之前需要调用 pthread_mutex_destroy();
int pthread_mutex_init (pthread_mutex_t *restrict mutex , const pthread_mutexattr_t *restrict attr);
第一个参数时要初始化的互斥量,第二个参数是互斥量的属性,默认为NULL;
int pthread_mutex_destroy ( pthread_mutex_t *mutex) ;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
加锁 :
int pthread_mutex_lock ( pthread_mutex_t *mutex);
成功返回0 ,失败返回错误码。如果互斥量已经被锁住,则导致该线程阻塞
int pthread_mutex_trylock ( pthread_mutex *mutex);
成功返回0 ,失败返回错误码。如果互斥量已经被锁住,不会该线程阻塞
解锁:
int pthread_mutex_unlock ( pthread_mutex_t *mutex);
成功返回0 ;失败返回错误码
1 #include "apb.h" 2 3 struct student 4 { 5 int id; 6 int age; 7 int name; 8 9 }stu; 10 11 int i; 12 13 pthread_mutex_t mutex; 14 15 void *thread_fun1(void *arg) 16 { 17 while(1) 18 { 19 stu.id = i; 20 stu.age = i; 21 stu.name = i; 22 i++; 23 if( stu.id != stu.age || stu.id != stu.name || stu.age != stu.name) 24 { 25 printf("thread1 %d,%d,%d ",stu.id,stu.age,stu.name); 26 break; 27 } 28 } 29 30 return (void *)0; 31 } 32 33 void *thread_fun2(void *arg) 34 { 35 while(1) 36 { 37 stu.id = i; 38 stu.age = i; 39 stu.name = i; 40 i++; 41 if( stu.id != stu.age || stu.id != stu.name || stu.age != stu.name) 42 { 43 printf("thread2%d,%d,%d ",stu.id,stu.age,stu.name); 44 break; 45 } 46 } 47 48 return (void *)0; 49 } 50 51 int main() 52 { 53 pthread_t tid1,tid2; 54 int err; 55 56 err = pthread_create (&tid1, NULL, thread_fun1, NULL); 57 if(err != 0) 58 { 59 printf("create new thread1 failure "); 60 return 0; 61 } 62 63 err = pthread_create (&tid2, NULL, thread_fun2, NULL); 64 if(err != 0) 65 { 66 printf("create new thread2 failure "); 67 return 0; 68 } 69 70 pthread_join(tid1, NULL); 71 pthread_join(tid2, NULL); 72 return 0; 73 74 } 75 76 结果: 77 thread2646248,646248,646248
1 #include "apb.h" 2 3 struct student 4 { 5 int id; 6 int age; 7 int name; 8 9 }stu; 10 11 int i; 12 13 pthread_mutex_t mutex; 14 15 void *thread_fun1(void *arg) 16 { 17 while(1) 18 { 19 pthread_mutex_lock(&mutex); 20 stu.id = i; 21 stu.age = i; 22 stu.name = i; 23 i++; 24 if( stu.id != stu.age || stu.id != stu.name || stu.age != stu.name) 25 { 26 printf("thread1 %d,%d,%d ",stu.id,stu.age,stu.name); 27 break; 28 } 29 pthread_mutex_unlock(&mutex); 30 } 31 32 return (void *)0; 33 } 34 35 void *thread_fun2(void *arg) 36 { 37 pthread_mutex_lock(&mutex); 38 while(1) 39 { 40 stu.id = i; 41 stu.age = i; 42 stu.name = i; 43 i++; 44 if( stu.id != stu.age || stu.id != stu.name || stu.age != stu.name) 45 { 46 printf("thread2%d,%d,%d ",stu.id,stu.age,stu.name); 47 break; 48 } 49 } 50 51 pthread_mutex_unlock(&mutex); 52 return (void *)0; 53 } 54 55 int main() 56 { 57 pthread_t tid1,tid2; 58 int err; 59 60 err = pthread_mutex_init (&mutex , NULL); 61 if (err != 0) 62 { 63 printf("init mutex failure "); 64 return 0; 65 } 66 err = pthread_create (&tid1, NULL, thread_fun1, NULL); 67 if(err != 0) 68 { 69 printf("create new thread1 failure "); 70 return 0; 71 } 72 73 err = pthread_create (&tid2, NULL, thread_fun2, NULL); 74 if(err != 0) 75 { 76 printf("create new thread2 failure "); 77 return 0; 78 } 79 80 pthread_join(tid1, NULL); 81 pthread_join(tid2, NULL); 82 return 0; 83 84 }
读写锁
读写锁与互斥量类似,不过读写锁由更高的并行性。不过读写锁有更高的并行性。互斥量要么加锁,要么不加锁,而且同一时刻值允许一个线程对其加锁,对于一个变量的读取,完全可以让多个线程同时进行操作。
定义: pthread_rwlock_t rwlock;
读写锁有三种状态,读模式下加锁,不加锁。一次只有一个线程可以占有写模式的读写锁,但多个线程科同时占有读模式的读写锁。
读写锁在写加锁状态时,在它被锁之前,所有试图对这个锁加锁的线程都会被阻塞。读写锁在读加锁状态时,所有试图以读模式对其加锁的线程都会获得访问权,但如果线程希望以写模式对其加锁,它必须阻塞直到所有的线程释放。
当读写锁——读模式加锁时,如果有线程试图以写模式对齐加锁,那么读写锁会阻塞随后的读模式锁请求,这样可以避免读锁长期占用,而写锁达不到请求
读写锁非常适合对数据结构读次数大于写次数的程序,当它以读模式锁住时,时以共享的方式锁住的;当以写模式锁住时,是以独占模式锁住的
初始化:
int pthread_rwlock_init(pthread_rwlock_t * rwlock, const pthread_rwlockattr_t * attr);
读模式加锁:
int pthread_rwlock_rdlock ( pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock ( pthread_rwlock_t *rwlock);
写模式加锁:
int pthread_rwlock_wrlock( pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock( pthread_rwlock_t *rwlock);
解锁:
int pthread_rwlock_unlock( pthread_rwlock_t *rwlock);
使用完需要销毁:
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
成功返回0;
1 读锁:共享关系 2 #include "apb.h" 3 4 int num = 0; 5 pthread_rwlock_t rwlock; 6 void *thread_fun1(void *s) 7 { 8 pthread_rwlock_rdlock(&rwlock); 9 printf("thread1 printf num %d ",num); 10 sleep(5); 11 printf("thread1 is over "); 12 13 pthread_rwlock_unlock(&rwlock); 14 return (void *)1; 15 } 16 17 void *thread_fun2(void *s) 18 { 19 pthread_rwlock_rdlock(&rwlock); 20 printf("thread2 printf num %d ",num); 21 sleep(5); 22 printf("thread2 is over "); 23 24 pthread_rwlock_unlock(&rwlock); 25 return (void *)1; 26 } 27 28 int main() 29 { 30 pthread_t tid1,tid2; 31 int err; 32 33 err = pthread_rwlock_init(&rwlock,NULL); 34 if(err != 0) 35 { 36 printf("rwlock init failure "); 37 return 0; 38 } 39 40 err = pthread_create (&tid1, NULL ,thread_fun1 ,NULL); 41 if(err != 0) 42 { 43 printf("create new thread1 failure "); 44 return 0; 45 } 46 47 err = pthread_create (&tid2, NULL ,thread_fun2 ,NULL); 48 if(err != 0) 49 { 50 printf("create new thread2 failure "); 51 return 0; 52 } 53 54 pthread_join(tid1,NULL); 55 pthread_join(tid2,NULL); 56 57 pthread_rwlock_destroy(&rwlock); 58 return 0; 59 60 } 61 62 结果: 63 thread1 printf num 0 64 thread2 printf num 0 65 (延时5s) 66 thread2 is over 67 thread1 is over 68 69 写锁: 70 #include "apb.h" 71 72 int num = 0; 73 pthread_rwlock_t rwlock; 74 void *thread_fun1(void *s) 75 { 76 pthread_rwlock_wrlock(&rwlock); 77 printf("thread1 printf num %d ",num); 78 sleep(5); 79 printf("thread1 is over "); 80 81 pthread_rwlock_unlock(&rwlock); 82 return (void *)1; 83 } 84 85 void *thread_fun2(void *s) 86 { 87 pthread_rwlock_rdlock(&rwlock); 88 printf("thread2 printf num %d ",num); 89 sleep(5); 90 printf("thread2 is over "); 91 92 pthread_rwlock_unlock(&rwlock); 93 return (void *)1; 94 } 95 96 int main() 97 { 98 pthread_t tid1,tid2; 99 int err; 100 101 err = pthread_rwlock_init(&rwlock,NULL); 102 if(err != 0) 103 { 104 printf("rwlock init failure "); 105 return 0; 106 } 107 108 err = pthread_create (&tid1, NULL ,thread_fun1 ,NULL); 109 if(err != 0) 110 { 111 printf("create new thread1 failure "); 112 return 0; 113 } 114 115 err = pthread_create (&tid2, NULL ,thread_fun2 ,NULL); 116 if(err != 0) 117 { 118 printf("create new thread2 failure "); 119 return 0; 120 } 121 122 pthread_join(tid1,NULL); 123 pthread_join(tid2,NULL); 124 125 pthread_rwlock_destroy(&rwlock); 126 return 0; 127 128 } 129 130 结果: 131 thread2 printf num 0 132 (延时5s) 133 thread2 is over 134 thread1 printf num 0 135 (延时5s) 136 thread1 is over
条件变量
当互斥量被锁住以后,发现当前线程还是无法完成自己的操作,那它应该释放互斥量,让其他线程工作。
方式:(1,采用轮询的方式,不停的查询需要的条件 ; 2,让系统查询,使用条件变量 pthread_cond_t cond ; )
初始化:
1, pthread_cond_t cond = PTHREAD_COND_INITALIZER ;
2, int pthread_cond_init ( pthread_cond_t *restrict cond , const pthread_condattr_t *restrict attr); (初始量,属性) 默认属性为空 NULL
销毁:
int pthread_cond_destroy ( pthread_cond_t *cond);
使用:
条件变量的使用需要配合互斥量
int pthread_cond_wait ( pthread_cond_t *restrict cond , pthread_mutex_t *restrict mutex);
1,使用pthread_cond_wait() 等待条件变为真。传递给pthread_cond_wait () 的互斥量对条件进行保护,调用者把锁住的互斥量传递给函数;
2,这个函数将线程放到等待条件的线程列表上,然后对互斥量进行解锁,这是个原子操作。当条件满足时这个函数返回,返回后继续对互斥量加锁。
int pthread_cond_timewait ( pthread_cond_t *restrict cond , pthread_mutex_t *restrict mutex , const struct timespec *restrict abstime);
3, 这个函数与pthread_cond_wait 类似 , 只是多一个timeout , 如果到了指定的时间条件还不满足,那么就返回,时间用下面结构体表示
struct timespec {
time_t tv_sec;
long tv_nsec;
};
注意: 这个时间时绝对时间,例如等待3分钟,就要把当前时间加上3分钟后转换到timespec , 而不是直接将3分钟转化
当条件满足的时候,需要唤醒等待条件的线程
int pthread_cond_broadcast ( pthread_cond_t *cond);
int pthread_cond_signal ( pthread_cond_t *cond);
1,pthread_cond_broadcast 唤醒等待条件的所有线程
2,pthread_cond_signal 至少唤醒等待条件的某一个线程
注意: 一定要在条件改变后再唤醒线程
1 #include "apb.h" 2 3 #define BUFFER_SIZE 5 //产品库存大小 4 #define PRODUCT_CNT 30 //产品生产总数 5 6 struct produte_cons 7 { 8 int buffer[BUFFER_SIZE]; //生产产品值 9 pthread_mutex_t lock; //互斥锁 volatile int 10 int readpos , writepos; //读写位置 11 pthread_cond_t notempty; //条件变量 ,非空 12 pthread_cond_t notfull; //非满 13 14 }buffer; 15 16 void init(struct produte_cons *p) 17 { 18 pthread_mutex_init( &p -> lock,NULL); //互斥锁 19 pthread_cond_init( &p -> notempty,NULL);//条件变量 20 pthread_cond_init( &p -> notfull ,NULL);//条件变量 21 p -> readpos = 0; //读写位置 22 p -> writepos = 0; 23 24 } 25 26 void finish(struct produte_cons *p) 27 { 28 pthread_mutex_destroy(&p -> lock); 29 pthread_cond_destroy(&p -> notempty); 30 pthread_cond_destroy(&p -> notfull); 31 p -> readpos = 0; 32 p -> writepos = 0; 33 34 } 35 36 37 //储存一个数据到buffer 38 void put (struct produte_cons *p , int data) //输入产品子函数 39 { 40 pthread_mutex_lock (&p ->lock); 41 if((p -> writepos+1)%BUFFER_SIZE== p->readpos) 42 { 43 printf("producer wait fir not full "); 44 pthread_cond_wait( &p -> notfull , &p -> lock); 45 46 //这里,生产者 notfull 等待消费者 pthread_cond_signal(&p->notfull);信号 47 //如果,消费者发送了 signal 信号,表示有了 空闲 48 } 49 50 p -> buffer[ p -> writepos] = data; 51 p -> writepos ++; 52 if (p -> writepos >= BUFFER_SIZE) 53 p -> writepos = 0; 54 pthread_cond_signal ( &p -> notempty); 55 pthread_mutex_unlock (&p -> lock); 56 } 57 58 //读,移除一个数据从buffer 59 int get(struct produte_cons *p) 60 { 61 int data; 62 63 pthread_mutex_lock (&p -> lock); 64 65 if(p ->readpos == p -> writepos) 66 { 67 printf("consumer wait for not empty "); 68 pthread_cond_wait (&p ->notempty , &p ->lock); 69 } 70 71 data = p ->buffer[p ->readpos]; 72 p ->readpos ++; 73 74 if(p ->readpos >= BUFFER_SIZE) 75 p ->readpos = 0; 76 pthread_cond_signal (&p -> notfull); 77 pthread_mutex_unlock (&p -> lock); 78 79 return data; 80 } 81 82 void *producer(void *data) //子线程 ,生产 83 { 84 int n; 85 for(n = 1;n<50; ++n)//生产50个产品 86 { 87 sleep(1); 88 printf("put the %d produte ... ",n); 89 put(&buffer ,n); 90 printf("put the %d produte succes ",n); 91 92 } 93 94 printf("producer stopped "); 95 return NULL; 96 } 97 98 99 void *consumer (void *data) 100 { 101 static int cnt = 0; 102 int num; 103 while(1) 104 { 105 sleep(2); 106 printf("get product ... "); 107 num = get(&buffer); 108 printf("get the %d product success ",num); 109 if (++cnt == PRODUCT_CNT) 110 break; 111 112 } 113 printf("consumer stopped "); 114 return NULL; 115 116 } 117 118 int main(int argc, char *argv[]) 119 { 120 121 pthread_t th_a,th_b; 122 void *retval; 123 124 init(&buffer); 125 126 pthread_create(&th_a , NULL , producer ,0); 127 pthread_create(&th_b , NULL , consumer ,0); 128 129 pthread_join(th_a , &retval); 130 pthread_join(th_b , &retval); 131 132 finish(&buffer); 133 134 return 0; 135 } 136 137 138 结果: 139 140 put the 1 produte ... 141 put the 1 produte succes 142 get product ... 143 get the 1 product success 144 put the 2 produte ... 145 put the 2 produte succes 146 put the 3 produte ... 147 put the 3 produte succes 148 get product ... 149 get the 2 product success 150 put the 4 produte ... 151 put the 4 produte succes 152 put the 5 produte ... 153 put the 5 produte succes 154 get product ... 155 get the 3 product success 156 put the 6 produte ... 157 put the 6 produte succes 158 put the 7 produte ... 159 put the 7 produte succes 160 get product ... 161 get the 4 product success 162 put the 8 produte ... 163 put the 8 produte succes 164 put the 9 produte ... 165 producer wait fir not full 166 get product ... 167 get the 5 product success 168 put the 9 produte succes 169 put the 10 produte ... 170 producer wait fir not full 171 get product ... 172 get the 6 product success 173 put the 10 produte succes
线程的高级属性:
1)一次性初始化:
通常当初始化应用程序时,可以比较容易地将其放在main()中。但当写一个库函数时,就不能在main() 函数中初始化了,可以用静态初始化,但使用一次性初始化会更简单
首先定义一个pthread_once_t 的变量,这个变量要用宏PTHREAD_ONCE_INIT 初始化。然后创建一个与控制变量相关的初始化函数
pthread_once_t once_control = PTHREAD_ONCE_INIT
void init_routine()
{
//初始化互斥量
//初始化读写锁
}
接下来就可以在任何时候调用pthread_once函数
int pthread_once ( pthread_once_t * once_control , void ( *init_routine)(void)); eg: pthread_once(&once , thread_fun1);
功能:此函数使用初值为PTHREAD_ONCE_INIT 的once_control 变量 保证init_routine() 函数在本进程执行序列中仅执行一次。在多线程编程环境下,尽管 pthread_once() 调用会出现在多个线程中, init_routine() 函数仅执行一次,在哪个线程中执行是由内核调度决定的。
实际“一次性函数” 的执行状态由三种 :NEVER 从未(0) ; IN_PROGRESS (1)正在执行 ; DONE 完毕 (2) .用once_control来表示pthread_once() 的执行状态:
1) 如果once_control的初值为0 ,那么pthread_once 从未执行过,init_routine()函数会执行
2) 如果once_control的初值为1,则由于所有pthread_once() 都必须等待其中一个激发“已执行一次”的信号,因此所有pthread_once() 都会陷入等待,init_control()就无法执行
3) 如果once_control的初值为2,则表示pthread_once() 已执行过一次,从而所有pthread_once() 都会立即返回,init_control()就没有机会执行
当pthread_once 的函数成功返回,once_control就会被设置为2.
****************************************************************************************************************************************************************************************************************************************************************
线程属性:
1,线程的属性
线程的属性用pthread_attr_t 类型结构表示,在创建线程的时候可以不用传入NULL,而是传入一个pthread_attr_t 结构,由用户自己配置线程的属性
pthread_attr_t 类型对应用线程是不透明的,也就是说应用不需要了解有关属性对象内部结构的任何细节,因而可以增加线程的可移植性
线程的属性:
名称 | 描述 |
detachstate | 线程的分离状态 |
guardsize | 线程栈末尾的警戒区域大小(字节数) |
stacksize | 线程栈的最低地址 |
stacksize | 线程栈的大小(字节数) |
并不是所有的系统都支持线程的这些属性,因此需要检查当前系统是否支持;还有一些属性不包括在pthread_attr_t 内,如:线程的可取消状态,取消类型,并发度。
线程属性初始化和销毁
线程属性初始化:
int pthread_attr_init ( pthread_attr_t *attr);
线程属性销毁:
int pthread_attr_destroy( pthread_attr_t *attr);
如果在点用pthread_attr_init 初始化属性的时候分配了内存空间,那么pthread_attr_destroy() 将释放内存空间。除此之外,pthread_attr_destroy 还会用无效的值初始化pthread_dttr_t 对象,因此如果该属性对象呗误用,会导致创建线程失败。
2,线程的分离属性:
分离属性的使用方法:
如果在创建线程的时候就知道不需要了解线程的终止状态,那么可以修改pthread_attr_t 结构体的detachstate 属性,让线程以分离状态启动。可以使用pthread_attr_setdetachstate函数来设置线程的分离状态属性。线程的分离属性由两种合法值:
PTHREAD_CREATE_DETACHED 分离的
PTHREAD_CREATE_JOINABLE 非分离的 ,可以连接
int pthread_attr_setdetachstate ( pthread_attr_t *attr , int detachstate);
int pthread_attr_getdetachstate ( pthread_attr_t *attr , int *detachstate); 可获取线程的分离状态属性
设置线程分离的步奏:
1,定义线程属性变量 pthread_attr_t attr
2,初始化 attr,, pthread_attr_init (&attr);
3,设置线程为分离或非分离 pthread_attr_setdetachstate ( &attr , detachstate);
4,创建线程pthread_create ( &tid , &attr , thread_fun , NULL);
所有的系统都会支持线程的分离状态属性。
1 #include "apb.h" 2 3 int num = 0; 4 pthread_attr_t attr; 5 void *thread_fun1(void *s) 6 { 7 printf("thread1 printf num %d ",num); 8 return (void *)1; 9 } 10 11 void *thread_fun2(void *s) 12 { 13 printf("thread2 printf num %d ",num); 14 return (void *)2; 15 } 16 17 int main() 18 { 19 pthread_t tid1,tid2; 20 int err; 21 22 pthread_attr_init(&attr); 23 pthread_attr_setdetachstate(&attr , PTHREAD_CREATE_JOINABLE); 24 // pthread_attr_setdetachstate(&attr , PTHREAD_CREATE_DETACHED); 25 26 err = pthread_create (&tid1, &attr ,thread_fun1 ,NULL); 27 if(err != 0) 28 { 29 printf("create new thread1 failure "); 30 return 0; 31 } 32 33 err = pthread_create (&tid2, &attr ,thread_fun2 ,NULL); 34 if(err != 0) 35 { 36 printf("create new thread2 failure "); 37 return 0; 38 } 39 40 pthread_join(tid1,NULL); 41 pthread_join(tid2,NULL); 42 43 pthread_attr_destroy(&attr); 44 45 return 0; 46 } 47 48 结果: 49 thread2 printf num 0 50 thread1 printf num 0 51 52 53 如果用DETACHED ,则join失败,新进程不会执行
3,线程栈属性:用ulimit -s 查看默认栈大小
1、 线程的栈大小与地址;
对于进程来说,虚拟地址空间的大小是固定的,进程中只有一个栈。因此它的大小通长不是问题。但对线程来说,同样的虚拟地址被所有的线程共享。如果应用程序使用了太多的线程,致使栈累计超过可用的虚拟地址空间,这个时候就需要减少线程默认栈的大小。另外,如果线程分配了大量的自动变量或者线程的栈帧太深,那么这个时候需要的栈要比默认的大。
如果用完了虚拟地址空间,可以使用malloc 或者 mmap 来为其他栈分配空间,并修改栈的位置。
修改栈属性:
int pthread_attr_setstack ( pthread_attr_t *attr , void *stackaddr , size_t stacksize);
获取栈属性
int pthread_attr_getstack ( pthread_attr_t *attr , void **stackaddr , size_t *stacksize);
参数:stackaddr 是栈的内存单元最低地址 , 参数 stacksize 是栈的大小。需要注意: stackaddr 并不一定是栈的开始,对于一些处理器,栈的地址是从高往低,那么这时 stackaddr 时栈的结尾。
当然也可以单独获取或者修改栈的大小,而不去修改栈的地址。对于栈大小设置,不能小于PTHREAD_STACK_MIN (需要头文件 limit.h)
设置栈大小:
int pthread_attr_setstacksize ( pthread_attr_t *attr , size_t stacksize);
获取栈大小:
int pthread_attr_getstacksize ( pthread_attr_t *attr , aisze_t *stacksize);
对于栈大小的设置,在创建线程之后,还可以修改。
对于遵循POSIX 标准的系统来说,不一定要支持线程的栈属性,需要检查
1)在编译阶段使用
_POSIX_THREAD_ATTR_STACKADDR 和 _POSIX_THREAD_ATTR_STACKSIZE 符号来检查系统是否支持线程栈属性
2)在运行阶段
_SC_THREAD_ATTR_STACKADD 和 _SC_THREAD_THREAD_ATTR_STACKSIZE 传给syconf () 函数检查系统对线程栈属性的支持
2、 栈尾警戒区
线程属性guardsize 控制着线程栈末尾以后用以避免栈溢出的扩展内存的大小,这个属性默认PAGESIZE 个字节 。可以把它设置为0 , 这样就不会提供警戒缓冲区。同样的,如果修改了stackaddr , 系统会认为用户管理栈,警戒缓存区会无效。
设置guardsize:
int pthread_attr_setgusrdsize ( pthread_attr_t *attr , size_t guardsize);
获取guardsize:
int pthread_attr_getguardsize ( pthread_attr_t *attr ,size_t *guardsize);
1 #include "apb.h" 2 3 int num = 0; 4 pthread_attr_t attr; 5 void *thread_fun1(void *s) 6 { 7 size_t stacksize; 8 #ifdef _POSIX_THREAD_ATTR_STACKSIZE 9 pthread_attr_getstacksize(&attr , &stacksize); 10 printf("thread1 init stack size is %d ",stacksize); 11 pthread_attr_setstacksize(&attr , 16400); 12 pthread_attr_getstacksize(&attr , &stacksize); 13 printf("thread1 after stack size is %d ",stacksize); 14 15 #endif 16 return (void *)1; 17 } 18 19 20 int main() 21 { 22 pthread_t tid1; 23 int err; 24 25 pthread_attr_init(&attr); 26 pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_JOINABLE); 27 28 #ifdef _POSIX_THREAD_ATTR_STACKSIZE 29 pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); 30 #endif 31 err = pthread_create (&tid1, NULL ,thread_fun1 ,NULL); 32 if(err != 0) 33 { 34 printf("create new thread1 failure "); 35 return 0; 36 } 37 38 39 pthread_join(tid1,NULL); 40 return 0; 41 } 42 43 结果: 44 thread1 init stack size is 16384 45 thread1 after stack size is 16400
4,线程的同步属性:
1)互斥量的属性:
互斥量的属性用 pthread_mutexattr_t 类型数据表示,使用之前必须初始化,结束必须销毁:
初始化:
int pthread_mutexattr_init (pthread_mutexattr_t *attr);
销毁:
int pthread_mutexattr_destroy ( pthread_mutexattr_t *attr);
1,进程共享属性
共享进程属性由两种值:
PTHREAD_PROCESS_PRIVATE 这个是默认值,同一个进程中的多个线程间同一个同步对象
PTHREAD_PROCESS_SHARED 这个属性可以使互斥量在多个进程中同步进行,如果互斥量在多个进程的共享内存区域,那么具有这个属性的互斥量可以同步多进程
设置互斥量进程共享属性
int pthread_mutexattr_getpshared ( const pthread_mutexattr_t *restrict attr , int *restrict pshared);
int pthread_mutexattr_setpshared ( pthread_mutexattr_t *attr , int pshared);
进程共享属性需要检测系统是否支持,可以检测宏 _POSIX_THREAD_PROCESS_SHARED
2,类型属性
互斥量类型 | 没有解锁时再次加锁 | 不占用时加锁 | 已解锁时加锁 |
PTHREAD_MUTEX_NORMAL (默认类型) |
死锁 | 未定义 | 未定义 |
PTHREAD_MUTEX_ERRORCHEK (错误检查) | 返回错误 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_RECURSIVE (递归) | 允许 | 返回错误 | 返回错误 |
PTHREAD_MUTEX_DEFAULT (默认,请求系统设置为其他三种) | 未定义 | 未定义 | 未定义 |
设置互斥量的类型属性:
int pthread_mutexattr_gettype ( const pthread_mutexattr_t *attr , int *restrict type);
int pthread_mutexattr_settype ( pthread_mutexattr_t *attr , int type);
1 #include "apb.h" 2 3 int main() 4 { 5 char *shm = "myshm"; 6 char *shm1 = "myshm1"; 7 int shm_id,shm_id1; 8 char *buf; 9 pid_t pid; 10 11 //打开共享内存 12 shm_id = shm_open(shm , O_RDWR|O_CREAT , 0644); 13 //调整共享内存大小 14 ftruncate(shm_id ,100); 15 //映射共享内存,MAP_SHARED 属性表明,对共享内存的任何修改都会影响其他进程 16 buf = (char *)mmap(NULL , 100 , PROT_READ|PROT_WRITE , MAP_SHARED , shm_id ,0); 17 18 pid = fork(); 19 if(pid == 0) 20 { 21 //休眠一秒让父进程先运行 22 sleep(1); 23 printf("i am child proccess "); 24 25 //将共享内存内容改为hello 26 memcpy (buf , "hello" , 6); 27 printf("child buf is : %s ",buf); 28 29 } 30 else if (pid >0) 31 { 32 printf("i an parent proccess "); 33 34 //修改内容为world 35 memcpy(buf , "world",6); 36 sleep(3); 37 printf("parent buf is : %s ",buf); 38 } 39 40 //解除映射 41 munmap(buf , 100); 42 //消除共享内存 43 shm_unlink(shm); 44 return 0; 45 } 46 47 gcc a.c -lrt -o a 48 49 50 结果: 51 i an parent proccess 52 i am child proccess 53 child buf is : hello 54 parent buf is : hello
1 #include "apb.h" 2 int main() 3 { 4 char *shm = "myshm"; 5 char *shm1 = "myshm1"; 6 int shm_id,shm_id1; 7 char *buf; 8 pid_t pid; 9 10 pthread_mutex_t *mutex; 11 pthread_mutexattr_t mutexattr; 12 13 //打开共享内存 14 shm_id1 = shm_open(shm1 , O_RDWR|O_CREAT , 0644); 15 //调整共享内存大小 16 ftruncate(shm_id1 ,100); 17 //映射共享内存,MAP_SHARED 属性表明,对共享内存的任何修改都会影响其他进程 18 mutex = (pthread_mutex_t *)mmap(NULL , 100 , PROT_READ|PROT_WRITE , MAP_SHARED , shm_id1 ,0); 19 20 pthread_mutexattr_init(&mutexattr); 21 #ifdef _POSIX_THREAD_PROCESS_SHARED 22 pthread_mutexattr_setpshared(&mutexattr , PTHREAD_PROCESS_SHARED); 23 #endif 24 pthread_mutex_init(mutex, &mutexattr); 25 //打开共享内存 26 shm_id = shm_open(shm , O_RDWR|O_CREAT , 0644); 27 //调整共享内存大小 28 ftruncate(shm_id ,100); 29 //映射共享内存,MAP_SHARED 属性表明,对共享内存的任何修改都会影响其他进程 30 buf = (char *)mmap(NULL , 100 , PROT_READ|PROT_WRITE , MAP_SHARED , shm_id ,0); 31 32 pid = fork(); 33 if(pid == 0) 34 { 35 //休眠一秒让父进程先运行 36 sleep(1); 37 printf("i am child proccess "); 38 39 pthread_mutex_lock(mutex); 40 //将共享内存内容改为hello 41 memcpy (buf , "hello" , 6); 42 printf("child buf is : %s ",buf); 43 pthread_mutex_unlock(mutex); 44 } 45 else if (pid >0) 46 { 47 printf("i an parent proccess "); 48 49 pthread_mutex_lock(mutex); 50 //修改内容为world 51 memcpy(buf , "world",6); 52 sleep(3); 53 printf("parent buf is : %s ",buf); 54 pthread_mutex_unlock(mutex); 55 } 56 57 58 pthread_mutexattr_destroy(&mutexattr); 59 pthread_mutex_destroy(mutex); 60 61 munmap(buf,100); 62 shm_unlink(shm1); 63 //解除映射 64 munmap(buf , 100); 65 //消除共享内存 66 shm_unlink(shm); 67 return 0; 68 } 69 70 71 gcc a.c -lrt -pthread -o a 72 73 74 75 结果: 76 i an parent proccess 77 i am child proccess 78 parent buf is : world 79 child buf is : hello
2)读写锁
只有一个进程共享属性
初始化:
int pthread_rwlockattr_init( pthread_rwlockattr_t *attr);
销毁:
int pthread_rwlockattr_destroy ( pthread_rwlockattr_t *attr);
设置读写锁的进程共享属性:
int pthread_rwlockattr_getpshared ( const pthread_rwlockattr_t *restrict attr , int *restrict pshared);
int pthread_rwlockattr_setpshared ( pthread_rwlock_t *attr , int pshared);
3)条件变量
也有进程共享属性
初始化:
int pthread_condattr_init( pthread_condattr_t *attr);
销毁:
int pthread_condattr_destroy ( pthread_condattr_t *attr);
设置读写锁的进程共享属性:
int pthread_condattr_getpshared ( const pthread_condattr_t *restrict attr , int *restrict pshared);
int pthread_condattr_setpshared ( pthread_rwlock_t *attr , int pshared);
5,线程的私有数据
应用程序设计中有必要提供一种变里,使得多个函数多个线程都可以访问这个变量( 看起来是个全局变里),但是线程对这个变里的访问都不会彼此产生影响(貌似不是全局变里哦〉,但是你需要这样的数据,比如errno。那么这种数据就是线程的私有数据,尽管名字相同,但是每个线程访问的都是数据的副本。
在使用私有数据之前,首先要创建一个与私有数据相关的键,来获取对私有数据的访问权限。这个间哦的类型是 pthread_key_t
int pthread_key_create ( pthread_key_t *key , void (*destructor)(void *) );
创建的键放在key指向的内存单元里,destructor 是与键相关的析构函数 。当线程调用 pthread_exit 或者 使用return 返回,析构函数就会被调用。当析构函数调用的时候,它只有一个参数,这个参数是与key关联的那个数据的地址,因此可以在析构函数中将这个数据销毁。 键使用完也可以销毁,与它关联的数据并没有销毁
int pthread_key_delete ( pthread_key_t key);
有了这个键之后,就可以将私有数据和键关联起来,这样就可以通过键来找到数据。所有的线程都可以访问这个键,但他们可以为键关联不同的数据(名字一样,值却不同的全局变量)
int pthread_setspecific ( pthread_key_t key , const void *value); 将私有数据与键关联
void * pthread_getspecific ( pthread_key_t key); 获取私有数据的地址,如果没有数据与key 关联,那么返回空
1 #include "apb.h" 2 3 pthread_key_t key; 4 5 void *thread_fun1(void *s) 6 { 7 printf("thread1 start "); 8 int a =1; 9 pthread_setspecific(key , (void *)a); 10 sleep(2); 11 printf("thread1 key -> data is %d ", (int)pthread_getspecific(key)); 12 return (void *)1; 13 } 14 15 void *thread_fun2(void *s) 16 { 17 sleep(1); 18 printf("thread2 start "); 19 int a =2; 20 pthread_setspecific(key , (void *)a); 21 22 printf("thread2 key -> data is %d ",(int) pthread_getspecific(key)); 23 } 24 25 int main() 26 { 27 pthread_t tid1,tid2; 28 int err; 29 30 pthread_key_create(&key,NULL); 31 err = pthread_create (&tid1, NULL ,thread_fun1 ,NULL); 32 if(err != 0) 33 { 34 printf("create new thread1 failure "); 35 return 0; 36 } 37 38 err = pthread_create (&tid2, NULL ,thread_fun2 ,NULL); 39 if(err != 0) 40 { 41 printf("create new thread2 failure "); 42 return 0; 43 } 44 45 pthread_join(tid1,NULL); 46 pthread_join(tid2,NULL); 47 48 pthread_key_delete(key); 49 } 50 结果: 51 thread1 start 52 thread2 start 53 thread2 key -> data is 2 54 thread1 key -> data is 1
6,线程与fork()
当线程调用fork函数时,就为子进程创建了整个进程地址空间的副本,子进程通过继承整个地址空间的副本,也会将父进程的互斥量、读写锁、条件变量的状态继承过来。也就是说,如果父进程中互斥量是锁着的,那么在子进程中互斥量也是锁着的(尽管子进程自己还没有来得及lock),这是丰常不安全的,因为不是子进程自己锁住的,它无法解锁。
子进程内部只有一个线程,由父进程中调用fork函数的线程副本构成。如果调用fork的线程将互斥量锁住,那么子进程会拷贝一个pthread_ mutex_ lock副本,这样子进程就有机会去解锁了。或者互斥量根本就没被加锁,这样也是可以的,但是你不能确保永远是这样的情况。
pthread_ atfork函数给你创造了这样的条件,它会注册三个函数
int pthread_ atfork (void (*prepare) (void) , void (*parent) (void) , void (*child) (void) );
prepare是在fork调用之前会被调用的,parent在fork返回父进程之前调用,child在fork返回子进程之前调用。如果在prepare中加锁所有的互斥量,在parent和child中解锁所有的互斥量,那么在fork返回之后,互斥量的状态就是未加锁。
可以有多个pthread_ atfork 函数,这是也就会有多个处理程序( prepare, parent, child) 。多个prepare的执行顺序与注册顺序相反,而parent和child的执行顺序与注册顺序相同。
1 #include "apb.h" 2 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 3 4 void *thread_fun(void *s) 5 { 6 sleep(1); 7 pid_t pid; 8 9 pid = fork(); 10 if(pid==0) 11 { 12 pthread_mutex_lock(&mutex); 13 printf("child process "); 14 pthread_mutex_unlock(&mutex); 15 16 } 17 if(pid>0) 18 { 19 pthread_mutex_lock(&mutex); 20 printf("parent process "); 21 pthread_mutex_unlock(&mutex); 22 } 23 } 24 25 int main() 26 { 27 pthread_t tid; 28 29 if(pthread_create(&tid , NULL , thread_fun,NULL)) 30 { 31 printf("create thread failure "); 32 return; 33 34 } 35 pthread_mutex_lock(&mutex); 36 sleep(2); 37 printf("main "); 38 pthread_mutex_unlock(&mutex); 39 pthread_join(tid ,NULL); 40 41 pthread_mutex_destroy(&mutex); 42 return 0; 43 44 45 } 46 47 48 结果: 49 main 50 parent process 51 52 PID TTY TIME CMD 53 2375 pts/0 00:00:00 su 54 2376 pts/0 00:00:00 bash 55 2720 pts/0 00:00:00 vi 56 3036 pts/0 00:00:00 c 57 3047 pts/0 00:00:00 ps 58 59 c 进程阻塞
1 #include "apb.h" 2 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 3 4 void *thread_fun(void *s) 5 { 6 sleep(1); 7 pid_t pid; 8 9 pthread_mutex_lock(&mutex); 10 pid = fork(); 11 if(pid==0) 12 { 13 // pthread_mutex_lock(&mutex); 14 // printf("child process "); 15 pthread_mutex_unlock(&mutex); 16 printf("child process "); 17 18 } 19 if(pid>0) 20 { 21 // pthread_mutex_lock(&mutex); 22 // printf("parent process "); 23 pthread_mutex_unlock(&mutex); 24 printf("parent process "); 25 } 26 } 27 28 int main() 29 { 30 pthread_t tid; 31 32 if(pthread_create(&tid , NULL , thread_fun,NULL)) 33 { 34 printf("create thread failure "); 35 return; 36 37 } 38 // pthread_mutex_lock(&mutex); 39 // sleep(2); 40 printf("main "); 41 // pthread_mutex_unlock(&mutex); 42 pthread_join(tid ,NULL); 43 44 // pthread_mutex_destroy(&mutex); 45 return 0; 46 47 48 } 49 50 结果: 51 main 52 parent process 53 child process
1 #include "apb.h" 2 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 3 4 void prepare() 5 { 6 pthread_mutex_lock(&mutex); 7 printf("i am prepare "); 8 } 9 10 void parent() 11 { 12 pthread_mutex_unlock(&mutex); 13 printf("i am parent "); 14 15 } 16 17 void child() 18 { 19 pthread_mutex_unlock(&mutex); 20 printf("i am child "); 21 22 } 23 void *thread_fun(void *s) 24 { 25 sleep(1); 26 pid_t pid; 27 pthread_atfork(prepare , parent ,child); 28 pid = fork(); 29 if(pid==0) 30 { 31 pthread_mutex_lock(&mutex); 32 printf("child process "); 33 pthread_mutex_unlock(&mutex); 34 35 } 36 if(pid>0) 37 { 38 pthread_mutex_lock(&mutex); 39 printf("parent process "); 40 pthread_mutex_unlock(&mutex); 41 } 42 } 43 44 int main() 45 { 46 pthread_t tid; 47 48 if(pthread_create(&tid , NULL , thread_fun,NULL)) 49 { 50 printf("create thread failure "); 51 return; 52 53 } 54 // pthread_mutex_lock(&mutex); 55 // sleep(2); 56 printf("main "); 57 // pthread_mutex_unlock(&mutex); 58 pthread_join(tid ,NULL); 59 60 // pthread_mutex_destroy(&mutex); 61 return 0; 62 63 64 } 65 66 67 结果: 68 69 main 70 i am prepare 71 i am parent 72 parent process 73 i am child 74 child process