IPC——线程资源保护:互斥锁、信号量、条件变量

资源保护

进程的资源保护

对于进程来说,由于每个进程空间是完全独立的,相互间不可能篡改对方进程空间的数据,所以进程空间内部的数据(资源)保护的非常到位,不需要加什么额外的保护机制。只有当它们共享操作第三方资源时才会涉及到资源保护问题,比如共享操作第三方文件(或者共享内存)的数据时,才会使用到进程信号量这样的资源保护机制。我们在讲进程IPC的时候就说过,虽然进程信号量被划到“IPC”中,但是进程信号量的作用实际上是借助通信来实现资源(数据)保护。对于进程来说,因为进程空间的独立性,因此进程资源的保护很到位,反倒是进程间共享数据很困难,因此OS提供了管道、消息队列等进程间通信机制。

线程的资源保护

对于线程来说,由于进程内部的所有线程共享进程空间,因此线程间使用全局变量即可实现数据共享,数据通信的实现非常容易,不过数据共享越是容易,数据相互篡改的危险性就越高,因此对于线程来说,需要重点考虑如何保护资源(数据),防止相互篡改。

总结

进程:进程空间天然是独立的,因此进程间资源的保护是天然的(现成的),需要重点关心的进程间的通信

线程:多线程天然的共享进程空间,因此线程数据共享是天然的(现成的),需要重点关心的是资源的保护

线程的资源保护机制

C线程的资源保护机制有:互斥锁、信号量、条件变量

互斥锁

互斥锁的作用就是用来实现互斥的。原理同进程信号量那里的互斥。

互斥锁使用的步骤

①定义一个互斥锁(变量)

②初始化互斥锁:预设互斥锁的初始值

③加锁解锁

④进程退出时销毁互斥锁

初始化互斥锁的函数

原型 

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); 

:restrict是c99标准引入的,它只可以用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式.即它告诉编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改;这样做的好处是,能帮助编译器进行更好的优化代码,生成更有效率的汇编代码

功能

初始化定义的互斥锁。所谓初始化,就是设置互斥锁所需要的值。

参数

mutex:互斥锁,需要我们自己定义。

比如:pthread_mutex_t mutex;

pthread_mutex_t是一个结构体类型,所以mutex实际上是一个结构体变量。

attr:互斥锁的属性

设置NULL表示使用默认属性,除非我们想要实现一些互斥锁的特殊功能,否则默认属性就够用了。

返回值

总是返回0,所以这个函数不需要进行出错处理。

加锁解锁函数

原型 

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex); 

功能

pthread_mutex_lock:阻塞加锁

如果锁没有解开时,当前线程尝试加锁时会阻塞,直到加锁成功为止。

兄弟函数:pthread_mutex_trylock(pthread_mutex_t *mutex)

非阻塞加锁,加锁成功是最好,如果不成功就错误返回,不会阻塞。

pthread_mutex_unlock:解锁,解锁不会阻塞

参数

mutex:需要加锁和解锁的互斥锁

返回值

成功返回0,失败返回错误号。

销毁互斥锁函数

原型 

#include <pthread.h>                        
int pthread_mutex_destroy(pthread_mutex_t *mutex); 

功能

销毁互斥锁。所谓销毁,说白了就是删除互斥锁相关的数据,释放互斥锁数据所占用的各种内存资源。

参数

mutex:需要被销毁的互斥锁

返回值

成功返回0,失败返回非零错误号

再说说互斥锁

初始化互斥锁有两种方法

第1种:使用pthread_mutex_init实现

第2种:定义互斥锁时直接初始化实现

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 与 pthread_mutex_init(&mutex, NULL);的功能是一样的,都是将互斥锁设置为快锁。

怎么理解pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER ?

这句话的本来面目是:struct mutex*** mutex = {**,**,**,...};

这个是典型的结构体变量的初始化,pthread_mutex_t其实就是对struct mutex*** typedef后的类型,PTHREAD_MUTEX_INITIALIZER的宏值为{**,**,**,...}。

以下写法对不对 

pthread_mutex_t mutex;
mutex = PTHREAD_MUTEX_INITIALIZER; 

等价于

struct mutex*** mutex;
mutex = {**,**,**,...};

说白了这就是在尝试给结构体变量进行整体赋值,我们讲c时说过,结构体变量是不能够整体赋值的,所以写法是错误的。如果你想给结构体变量赋值的话,只能一个一个的给结构体成员赋值来实现。其实我们调用pthread_mutex_init函数来初始化互斥锁时,这个函数设置初始值的方式,就是给mutex这个结构体变量的成员一个一个的赋值来实现的。

所以说:

调用pthread_mutex_init函数来给mutex设置初始值时,实现的本质是结构体赋值。

使用pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER方式给mutex设置初始值时,实现的本质是结构体初始化。

代码演示

结合2种初始化互斥锁的方式。代码中我两种都用了,这种没问题,互斥锁大不了重复初始化一次,不影响

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <pthread.h>
  5 #include <string.h>
  6 #include <errno.h>
  7 #include <sys/types.h>
  8 #include <sys/stat.h>
  9 #include <fcntl.h>
 10 #include <signal.h>
 11 
 12 
 13 #define SECON_PTH_NUMS     2  //次线程数量
 14 #define PTHEXIT     -1    
 15 
 16 
 17 /* 传递给线程的参数 */
 18 typedef struct pthread_arg 
 19 {
 20     pthread_t tid;//存放线程tid
 21     int pthno;//我自己定义的编号
 22     int fd;//文件描述符
 23 }ptharg;
 24 
 25 
 26 
 27 struct gloable_va
 28 {
 29     ptharg pth_arg[SECON_PTH_NUMS];//结构体数组,每个元素会被当做参数传递给对应的次线程
 30     int pth_exit_flg[SECON_PTH_NUMS];//每个元素存放对应编号线程的退出状态
 31     pthread_attr_t attr;//存放线程新属性
 32     pthread_mutex_t mutex;//互斥锁
 33 }glbva = {.mutex = PTHREAD_MUTEX_INITIALIZER};
 34 
 35 void print_err(char *str, int line, int err_no)
 36 {
 37     printf("%d, %s:%s", line, str, strerror(err_no));
 38     exit(-1);
 39 }
 40 
 41 /* 线程退出处理函数 */
 42 void pth_exit_deal(void *arg)
 43 {
 44     pthread_t tid = ((ptharg *)arg)->tid;    
 45 
 46     printf("!!! pthread %lu exit\n", tid);
 47 }
 48 
 49 void *pth_fun(void *pth_arg)
 50 {    
 51     int fd = ((ptharg *)pth_arg)->fd;
 52     int pthno = ((ptharg *)pth_arg)->pthno;
 53     pthread_t tid = ((ptharg *)pth_arg)->tid;    
 54 
 55     //pthread_detach(pthread_self());//线程把自己分离出去
 56 
 57     //注册线程退出处理函数
 58     pthread_cleanup_push(pth_exit_deal, pth_arg);
 59     
 60     printf("pthno=%d, pthread_id=%lu\n", pthno, tid);
 61 
 62     while(1)
 63     {
 64         pthread_mutex_lock(&glbva.mutex);//加锁
 65         write(fd, "hello ", 6);
 66         write(fd, "world\n", 6);
 67         //检测退出状态
 68         if(glbva.pth_exit_flg[pthno] == PTHEXIT) break;
 69         pthread_mutex_unlock(&glbva.mutex);//解锁
 70     }
 71 
 72     
 73     pthread_cleanup_pop(!0);    
 74     return NULL;
 75     pthread_exit((void *)10);     
 76 }
 77 
 78 void signal_fun(int signo)
 79 {
 80     if(SIGALRM == signo)
 81     {
 82         int i = 0;
 83         for(i=0; i<SECON_PTH_NUMS; i++)
 84         {
 85             //pthread_cancel(glbva.pth_arg[i].tid);//取消次线程
 86             glbva.pth_exit_flg[i] = PTHEXIT;//设置为退出状态
 87         }
 88     }
 89     else if(SIGINT == signo)
 90     {
 91         exit(0);
 92     }
 93 }
 94 
 95 void process_exit_deal(void)
 96 {
 97     /* 销毁线程的属性设置 */
 98     int ret = 0;
 99     ret = pthread_attr_destroy(&glbva.attr);
100     if(ret != 0) print_err("pthread_attr_destroy fail", __LINE__, ret);
101     
102     /* 销毁互斥锁 */
103     ret = pthread_mutex_destroy(&glbva.mutex);
104     if(ret != 0) print_err("pthread_mutex_destroy fail", __LINE__, ret);
105 
106     printf("\nprocess exit\n");
107 }
108 
109 int main(void)
110 {
111     int fd = 0;
112     int i = 0;
113     int ret = 0;
114     
115     //注册进程退出处理函数,exit正常终止进程时弹栈调用
116     atexit(process_exit_deal);
117         
118     //初始化互斥锁
119     ret = pthread_mutex_init(&glbva.mutex, NULL);
120     if(ret != 0) print_err("pthread_mutex_init fail", __LINE__, ret);    
121 
122 
123     //打开文件,供线程操作,所有的线程(函数)可以共享打开的文件描述符
124     fd = open("./file", O_RDWR|O_CREAT|O_TRUNC, 0664);
125     if(fd == -1) print_err("open ./file fail", __LINE__, errno);
126     
127     /* 初始化护attr, 设置一些基本的初始值 */
128     ret = pthread_attr_init(&glbva.attr);
129     if(ret != 0) print_err("pthread_attr_init fail", __LINE__, ret);
130         
131     /* 设置分离属性 */
132     ret = pthread_attr_setdetachstate(&glbva.attr, PTHREAD_CREATE_DETACHED);
133     if(ret != 0) print_err("pthread_attr_setdetachstate fail", __LINE__, ret);
134 
135     /* 通过循环创建两个次线程 */
136     for(i=0; i<SECON_PTH_NUMS; i++)
137     {
138         glbva.pth_arg[i].fd = fd;//保存文件描述符
139         glbva.pth_arg[i].pthno = i; //我自己给的线程编号
140                                    //创建好次线程后,讲次线程分离
141         ret = pthread_create(&glbva.pth_arg[i].tid, &glbva.attr, pth_fun, (void *)&glbva.pth_arg[i]);    
142         if(ret != 0) print_err("pthread_create fail", __LINE__, ret);
143     }
144 
145     printf("main tid = %lu\n", pthread_self());
146 
147     signal(SIGINT, signal_fun);
148 
149     /* 定时5秒,时间到后取消次线程 */
150     signal(SIGALRM, signal_fun);
151     alarm(3);    
152 
153     #if 0
154     void *retval = NULL;
155     for(i=0; i<SECON_PTH_NUMS; i++)
156     {
157         //阻塞等待此线程结束,回收次线程资源,并通过第二个参数接受返回值
158         pthread_join(glbva.pth_arg[i].tid, &retval);
159         printf("@@ %ld\n", (long)retval);
160     }
161     #endif
162         
163 
164     while(1)
165     {
166         pthread_mutex_lock(&glbva.mutex);//加锁
167         write(fd, "hello ", 6);
168         write(fd, "world\n", 6);
169         pthread_mutex_unlock(&glbva.mutex);//解锁
170     }        
171     
172     return 0;
173 }
View Code

 

有关PTHREAD_MUTEX_INITIALIZER宏

实际上除了这个宏外,还有两个宏,分别是:

PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP

PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP

PTHREAD_MUTEX_INITIALIZER:快锁

快速互斥锁(或叫阻塞互斥锁),简称快锁。快锁的特点是:

①加锁不成功是会阻塞,如果不想阻塞必须使用pthread_mutex_trylock来加锁,而不是pthread_mutex_lock。

②对于同一把快锁来说,不能多次加锁,否者会出错

③已经解锁的快锁也不能再次解锁,否者会出错

PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP:检错互斥锁

使用pthread_mutex_lock加锁时,如果加锁不成功不会阻塞,会直接出错返回。加锁不成功就直接错误返回,所以才被称为“检错互斥锁”。

PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP:递归互斥锁。

特点:

①同一把锁可多次枷锁,每加一次锁,加锁次数就会加1

②解锁时,解锁的顺序刚好与加锁顺序相反,每解锁一次,加锁次数就会减1。

正是由于可以重复的加锁和解锁,所以才被称为递归加锁。

pthread_mutex_init(&mutex, NULL)设置是什么锁

当第二个参数为NULL时,默认设置的是快锁。如果你想通过pthread_mutex_init函数,将mutex初始化出“检错锁”和“递归锁”的话,我们必须通过第二个参数进行相应的属性设置来实现。这种实现方法比较麻烦。

如果你真想使用“检错锁”和“递归锁”,建议还是使用直接初始化的方式,这样会更方便。

pthread_mutex_t mutex = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP;

pthread_mutex_t mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;

 线程信号量

进程信号量与线程信号量

线程的信号量与进程的信号量几乎完全相同,只不过一个是给进程用的,另一个是给线程用的。我们使用进程信号量时,我们自己往往还需要二次封装,线程的信号量函数则不需要,直接就可以使用,所以线程的信号量使用起来更加容易,应该说使用难度非常低。

二值信号量和多值信号量

对于线程信号量来说,也分为二值信号量和多值信号量,同样的我们这里只讲二值信号量。使用二值信号量时,往往用来实现“互斥”和“同步”。如果想实现互斥的话,更多的还是使用前面讲的互斥锁来实现,因为线程互斥锁提供了更多可自供选择的功能,比如可以设置为“检错锁”、“递归锁”等。如果你只是想实现简单互斥的话,不管是使用线程互斥锁的“快锁”来实现,还是使用线程信号量来实现,最终所实现的互斥效果都是一样的。

信号量的使用步骤

①定义信号量集合

(a)用于互斥时,集合中只需要一个信号量。

(b)用于同步时,有几个线程需要同步,集合中就需要包含几个信号量

②初始化集合中的每个信号量

设置初始值,二值信号量的初始值要么是0、要么是1。

(a)如果是用于互斥,基本都是设置为1

(b)如果是用于同步,看具体情况

③p、v操作

p操作:信号量值-1

V操作:信号量值+1

④进程结束时,删除线程信号量集合

初始化信号量的函数

原型 

#include <semaphore.h>                                
int sem_init(sem_t *sem, int pshared, unsigned int value); 

功能

初始化线程信号量集合中的某个信号量,给它设置一个初始值。

参数

sem:信号量集合中的某个信号量

信号量集合需要我们自己定义,比如:sem_t sem[3]

线程信号量集合其实就是一个数组,数组每个元素就是一个信号量。

sem[0]:第一个信号量

sem[1]:第二个信号量

sem[2]:第三个信号量

sem_init(&sem[0], int pshared, unsigned int value);

线程信号量集合其实就是自定义的一个数组,而进程信号量集合则是通过semget函数创建的。我们只要把数组定义为全局变量,所有的线程即可共享使用,不像进程信号量,需要semid才能实现共享操作。

pshared

0:给线程使用

!0:可以给进程使用

不过对于进程来说,我们更多的还是使用进程信号量,因为线程信号量用到进程上时,存在一些不稳定的情况。

value:初始化值。对于二值信号量来说,要么是1,要么是0。

返回值

成功返回0,失败返回-1,errno被设置。注意信号量的错误号不是返回的,而是设置到errno中。

 PV操作函数

原型 

#include <semaphore.h>                                
int sem_wait(sem_t *sem);//阻塞P操作
int sem_post(sem_t *sem);//V操作 

功能

sem_wait:

阻塞p操作集合中某个信号量,值-1。如果能够p操作成功最好,否则就阻塞直到p操作操作成功为止。

sem_wait的兄弟函数

int sem_trywait(sem_t *sem):不阻塞

如果能够p操作就p操作,如果不能p操作就出错返回,不会阻塞。

int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

可以设置阻塞时间,如果能够p操作就p操作,不能就阻塞,如果在设置的时间内好没有p操作成功就是出错返回,不再阻塞。

sem_post

对某个信号量进行v操作,v操作不存在阻塞问题。v操作成功后,信号量的值会+1

参数

sem:p操作的某个信号量。比如:sem_wait(&sem[0]);

返回值

适用于2个函数,成功返回0,失败返回-1,errno被设置。

删除信号量函数

原型 

#include <semaphore.h>                                    
int sem_destroy(sem_t *sem); 

功能

删除某个信号量,把所有信号量都删除后,信号量集合就被销毁。这与删除进程信号量集合有所不同,对于进程信号量集合来说,只要删除一个信号量,整个集合即被删除,但是对于线程信号量来说,需要一个一个的删除,当所有信号量都删除完后,集合才被删除完毕。

参数

sem:信号量集合中某个信号量

返回值

成功返回0,失败返回-1,errno被设置。

代码演示

使用信号量实现互斥

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <pthread.h>
  5 #include <string.h>
  6 #include <errno.h>
  7 #include <sys/types.h>
  8 #include <sys/stat.h>
  9 #include <fcntl.h>
 10 #include <signal.h>
 11 #include <semaphore.h>
 12 
 13 #define SECON_PTH_NUMS     2  //次线程数量
 14 #define SEM_NUMS 1 //集合中信号量数量
 15 
 16 /* 传递给线程的参数 */
 17 typedef struct pthread_arg 
 18 {
 19     pthread_t tid;//存放线程tid
 20     int pthno;//我自己定义的编号
 21     int fd;//文件描述符
 22 }ptharg;
 23 
 24 struct gloable_va
 25 {
 26     ptharg pth_arg[SECON_PTH_NUMS];//结构体数组,每个元素会被当做参数传递给对应的次线程
 27     sem_t sem[SEM_NUMS];
 28     
 29 }glbva;
 30 
 31 void print_err(char *str, int line, int err_no)
 32 {
 33     printf("%d, %s:%s", line, str, strerror(err_no));
 34     exit(-1);
 35 }
 36 
 37 void *pth_fun(void *pth_arg)
 38 {    
 39     int fd = ((ptharg *)pth_arg)->fd;
 40 
 41     while(1)
 42     {
 43         sem_wait(&glbva.sem[0]);
 44         write(fd, "hello ", 6);
 45         write(fd, "world\n", 6);
 46         sem_post(&glbva.sem[0]);
 47     }
 48 
 49     return NULL;
 50 }
 51 
 52 void signal_fun(int signo)
 53 {
 54     int i = 0;
 55     int ret = 0;
 56 
 57     for(i=0; i<SEM_NUMS; i++)
 58     {
 59         ret = sem_destroy(&glbva.sem[i]);
 60         if(ret != 0) print_err("sem_destroy fail", __LINE__, ret);
 61     }
 62     exit(-1);
 63 }
 64 
 65 int main(void)
 66 {
 67     int fd = 0;
 68     int i = 0;
 69     int ret = 0;
 70 
 71 
 72     signal(SIGINT, signal_fun);    
 73     
 74     //打开文件,供线程操作,所有的线程(函数)可以共享打开的文件描述符
 75     fd = open("./file", O_RDWR|O_CREAT|O_TRUNC, 0664);
 76     if(fd == -1) print_err("open ./file fail", __LINE__, errno);
 77     
 78     /* 初始化信号量 */
 79     for(i=0; i<SEM_NUMS; i++)
 80     {    
 81         if(i == 0) ret = sem_init(&glbva.sem[i], 0, 1);
 82         else ret = sem_init(&glbva.sem[i], 0, 0);
 83         if(ret != 0) print_err("sem_init fail", __LINE__, errno);
 84     }
 85 
 86 
 87     /* 通过循环创建两个次线程 */
 88     for(i=0; i<SECON_PTH_NUMS; i++)
 89     {
 90         glbva.pth_arg[i].fd = fd;//保存文件描述符
 91         glbva.pth_arg[i].pthno = i; //我自己给的线程编号
 92                                    //创建好次线程后,讲次线程分离
 93         ret = pthread_create(&glbva.pth_arg[i].tid, NULL, pth_fun, (void *)&glbva.pth_arg[i]);    
 94         if(ret != 0) print_err("pthread_create fail", __LINE__, ret);
 95     }
 96 
 97     while(1)
 98     {    
 99         sem_wait(&glbva.sem[0]);
100         write(fd, "hello ", 6);
101         write(fd, "world\n", 6);
102         sem_post(&glbva.sem[0]);
103     }        
104     
105     return 0;
106 }
View Code

使用信号量实现同步

比如有三个线程(1主线程,2个次线程),分别打印333333、222222、111111,使用同步让他们顺序的打印111111、222222、333333。

使用进程信号量实现进程同步时,有多少个进程需要同步,集合中就需要包含几个信号量。同样的,使用线程信号量实现同步时,有几个线程需要同步,集合中就需要包含几个信号量。

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <pthread.h>
  5 #include <string.h>
  6 #include <errno.h>
  7 #include <sys/types.h>
  8 #include <sys/stat.h>
  9 #include <fcntl.h>
 10 #include <signal.h>
 11 #include <semaphore.h>
 12 
 13 #define SECON_PTH_NUMS     2  //次线程数量
 14 #define SEM_NUMS (SECON_PTH_NUMS + 1) //集合中信号量数量
 15 
 16 /* 传递给线程的参数 */
 17 typedef struct pthread_arg 
 18 {
 19     pthread_t tid;//存放线程tid
 20     int pthno;//我自己定义的编号
 21     int fd;//文件描述符
 22 }ptharg;
 23 
 24 struct gloable_va
 25 {
 26     ptharg pth_arg[SECON_PTH_NUMS];//结构体数组,每个元素会被当做参数传递给对应的次线程
 27     sem_t sem[SEM_NUMS];
 28     
 29 }glbva;
 30 
 31 void print_err(char *str, int line, int err_no)
 32 {
 33     printf("%d, %s:%s", line, str, strerror(err_no));
 34     exit(-1);
 35 }
 36 
 37 void *pth_fun1(void *pth_arg)
 38 {    
 39     while(1)
 40     {
 41         sem_wait(&glbva.sem[0]);
 42         printf("111111\n");
 43         sleep(1);
 44         sem_post(&glbva.sem[1]);
 45     }
 46 
 47     return NULL;
 48 }
 49 
 50 void *pth_fun2(void *pth_arg)
 51 {    
 52     while(1)
 53     {
 54         sem_wait(&glbva.sem[1]);
 55         printf("222222\n");
 56         sleep(1);
 57         sem_post(&glbva.sem[2]);
 58     }
 59 
 60     return NULL;
 61 }
 62 
 63 void signal_fun(int signo)
 64 {
 65     int i = 0;
 66     int ret = 0;
 67 
 68     for(i=0; i<SEM_NUMS; i++)
 69     {
 70         ret = sem_destroy(&glbva.sem[i]);
 71         if(ret != 0) print_err("sem_destroy fail", __LINE__, ret);
 72     }
 73     exit(-1);
 74 }
 75 
 76 int main(void)
 77 {
 78     int i = 0;
 79     int ret = 0;
 80     void *(*pth_fun_buf[])(void *) = {pth_fun1, pth_fun2};
 81 
 82     signal(SIGINT, signal_fun);    
 83 
 84     /* 初始化信号量 */
 85     for(i=0; i<SEM_NUMS; i++)
 86     {    
 87         if(i == 0) ret = sem_init(&glbva.sem[i], 0, 1);
 88         else ret = sem_init(&glbva.sem[i], 0, 0);
 89         if(ret != 0) print_err("sem_init fail", __LINE__, errno);
 90     }
 91 
 92 
 93     /* 通过循环创建两个次线程 */
 94     for(i=0; i<SECON_PTH_NUMS; i++)
 95     {
 96         ret = pthread_create(&glbva.pth_arg[i].tid, NULL, pth_fun_buf[i], NULL);
 97         if(ret != 0) print_err("pthread_create fail", __LINE__, ret);
 98     }
 99 
100     while(1)
101     {
102         sem_wait(&glbva.sem[2]);
103         printf("333333\n");    
104         sleep(1);
105         sem_post(&glbva.sem[0]);
106     }        
107     
108     return 0;
109 }
View Code

条件变量

线程配合工作的例子 

eg:主线程对va变量循环+1,次线程发现va==5时,打印va的值并将va清0,如果va的值!=5就什么都不做

采用最笨的实现方法:次线程循环检测va的值,然后做出相应的响应。代码如下

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <pthread.h>
 5 #include <string.h>
 6 #include <errno.h>
 7 #include <sys/types.h>
 8 #include <sys/stat.h>
 9 #include <fcntl.h>
10 #include <signal.h>
11 
12 #define SECON_PTH_NUMS     1  //次线程数量
13 int va=0;
14 
15 void print_err(char *str, int line, int err_no)
16 {
17     printf("%d, %s:%s", line, str, strerror(err_no));
18     exit(-1);
19 }
20 
21 void *pth_fun(void *pth_arg)
22 {    
23     while(1)
24     {
25         if(va==5)
26         {
27             printf("va=%d\n",va);
28             va=0;
29         }
30     }
31     return NULL;
32 }
33 
34 void signal_fun(int signo)
35 {
36     exit(0);
37 }
38 
39 int main(void)
40 {
41     int i = 0;
42     int ret = 0;
43     pthread_t tid;
44     ret = pthread_create(&tid, NULL, pth_fun, NULL);
45     if(ret != 0) print_err("pthread_create fail", __LINE__, ret);
46     while(1)
47     {
48         va=va+1;sleep(1);
49     }
50     return 0;
51 }
View Code

这种循环检测的方法虽然简单,但是存在很大的问题,那就是当va不满足时,次线程会一直在不停的循环检测,cpu执行次线程的while时其实是在空转,白白浪费cpu的资源。

最好的方式是,当va条件不满足时就应该让次线程休眠(阻塞),等主线程将va准备好时,主动通知次线程,将它唤醒,像这样的解决方式,我们就可以使用条件变量来实现。

条件变量的作用

多线程配合工作时,当线程检测到某条件不满足时就休眠,直到别的线程将条件准备好,然后通过条件变量将其唤醒。条件变量需要在互斥锁的配合下才能工作。

条件变量的使用步骤

①定义一个条件变量(全局变量)。由于条件变量需要互斥锁的配合,所以还需要定义一个线程互斥锁。

②初始化条件变量

③使用条件变量

④删除条件变量,也需要把互斥锁删除。

初始化条件变量函数

原型 

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); 

功能

初始化条件变量,与互斥锁的初始化类似。

也可以直接初始化:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//与互斥锁的初始化的原理是一样的

参数

cond:条件变量

attr:用于设置条件变量的属性,设置为NULL,表示使用默认属性

返回值

成功返回0,失败返回非零错误号

等待条件变量函数

原型 

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);     

功能

检测条件变量cond,如果cond没有被设置,表示条件还不满足,别人还没有对cond进行设置,此时pthread_cond_wait会休眠(阻塞),直到别的线程设置cond表示条件准备好后,才会被唤醒。

参数

cond:条件变量

mutex:和条件变量配合使用的互斥锁

返回值

成功返回0,失败返回非零错误号

兄弟函数

int pthread_cond_timedwait(pthread_cond_t *restrict cond, \
                                              pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
View Code

多了第三个参数,用于设置阻塞时间,如果条件不满足时休眠(阻塞),但是不会一直休眠,当时间超时后,如果cond还没有被设置,函数不再休眠。

设置条件变量函数

原型 

#include <pthread.h>                        
int pthread_cond_signal(pthread_cond_t *cond); 

功能

当线程将某个数据准备好时,就可以调用该函数去设置cond,表示条件准备好了,pthread_cond_wait检测到cond被设置后就不再休眠(被唤醒),线程继续运行,使用别的线程准备好的数据来做事。当调用pthread_cond_wait函数等待条件满足的线程只有一个时,就是用pthread_cond_signal来唤醒,如果说有好多线程都调用pthread_cond_wait在等待时,使用int pthread_cond_broadcast(pthread_cond_t *cond);它可以将所有调用pthread_cond_wait而休眠的线程都唤醒。

参数

cond:条件变量

返回值

成功返回0,失败返回非零错误号

删除条件变量函数

原型 

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond); 

功能

删除条件变量

参数

cond:条件变量

返回值

成功返回0,失败返回非零错误号

代码演示

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <unistd.h>
 4 #include <pthread.h>
 5 #include <string.h>
 6 #include <errno.h>
 7 #include <sys/types.h>
 8 #include <sys/stat.h>
 9 #include <fcntl.h>
10 #include <signal.h>
11 
12 
13 #define SECON_PTH_NUMS     1  //次线程数量
14 
15 int va = 0;
16 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
17 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
18 
19 
20 void print_err(char *str, int line, int err_no)
21 {
22     printf("%d, %s:%s", line, str, strerror(err_no));
23     exit(-1);
24 }
25 
26 void signal_fun(int signo)
27 {
28     if(SIGINT == signo)
29     {
30         pthread_cond_destroy(&cond);
31         pthread_mutex_destroy(&mutex);
32         exit(0);
33     }
34     else if(SIGQUIT == signo)
35     {
36         printf("%lu\n", pthread_self());
37     }
38 }
39 
40 void *pth_fun(void *pth_arg)
41 {    
42     while(1)
43     {
44         pthread_mutex_lock(&mutex);
45         //之所以将mutex传递给该函数,是因为害怕休眠后导致锁没有解开,
46         //使得其他线程不能使用这个互斥锁,把mutex传递给该函数的目的
47         //就是希望该函数如果检查cond没有被设置而休眠时,将Mutex解锁,
48         //让其它线程能够使用这个锁
49         pthread_cond_wait(&cond, &mutex);
50         printf("va = %d\n", va);
51         va = 0;
52         pthread_mutex_unlock(&mutex);
53     }
54     
55     return NULL;
56 }
57 
58 
59 int main(void)
60 {
61     int i = 0;
62     int ret = 0;
63     pthread_t tid;
64 
65     signal(SIGINT, signal_fun);
66 
67     //初始化条件变量    
68     ret = pthread_cond_init(&cond, NULL);
69     if(ret != 0) print_err("pthread_cond_init fail", __LINE__, ret);
70 
71 
72     ret = pthread_create(&tid, NULL, pth_fun, NULL);
73     if(ret != 0) print_err("pthread_create fail", __LINE__, ret);
74 
75 
76     printf("main %lu\n", pthread_self());
77     while(1)
78     {    
79         pthread_mutex_lock(&mutex);
80         va = va + 1;
81         
82         if(va == 5)
83         {
84             pthread_cond_signal(&cond);
85         }    
86         pthread_mutex_unlock(&mutex);
87 
88         sleep(1);
89     }
90 
91     return 0;
92 }
View Code

 

 

 

转载于:https://www.cnblogs.com/kelamoyujuzhen/p/9439873.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值