开发使用多线程过程中, 不可避免的会出现多个线程同时操作同一块共享资源, 当操作全部为读时, 不会出现未知结果, 一旦当某个线程操作中有写操作时, 就会出现数据不同步的事件.
而出现数据混乱的原因:
资源共享(独享资源则不会)
调试随机(对数据的访问会出现竞争)
线程间缺少必要的同步机制
以上三点, 前两点不能被改变. 欲提高效率, 传递数据, 资源必须共享. 只要资源共享, 就一定会出现线程间资源竞争, 只要存在竞争关系, 数据就会出现混乱.
所以只能从第三点着手, 使多个线程在访问共享资源的时候, 出现互斥.
线程同步:
指在一定的时间里只允许某一个进程访问某个资源,而在此时间内,不允许其它线程对该资源进行操作.
线程的同步机制:
-
互斥量(互斥锁)
读写锁
条件变量(需要配合互斥量(互斥锁)来使用)
信号量
由于前章节介绍完了互斥量(互斥锁)和读写锁, 本次介绍条件变量
条件变量(需要配合互斥量(互斥锁)来使用):
条件变量本身不是锁! 但它可以造成阻塞.通常与互斥量(互斥锁)一起使用,给多线程提供一个会合的场所.给互斥量(互斥锁)一层包装.
条件变量主要用的API:
初始化条件变量
pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参数1:cond 传入的一个条件变量地址
参数2:attr 表示条件变量的属性,没有特殊需求一般填NULL
restrice: C99标准类型限定符, 用于告诉编译器,该指向的对象已被引用,不能被除该指针外的所有直接或间接的方法修改该指向的内容.
销毁一个条件变量
pthread_cond_destroy(pthread_cond_t* cond);
参数: cond 传入的一个条件变量地址
阻塞等待一个条件变量
pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
参数1: cond传入一个已经初始好的条件变量地址
参数2: mutex传入一个已经初始化好的互斥量(互斥锁)
restrice: C99标准类型限定符, 用于告诉编译器,该指向的对象已被引用,不能被除该指针外的所有直接或间接的方法修改该指向的内容.
函数作用:
1.阻塞等待条件变量(参1)满足
2.释放已掌握的护持锁(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
1. 2两步为一个原子操作
3.当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取护持锁;
限时等待一个条件变量:
pthread_cond_timewait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
参数1: cond传入一个已经初始好的条件变量地址
参数2: mutex传入一个已经初始化好的互斥量(互斥锁)
参数3: abstime传入一个绝对时间解构体, struct timespes结构体
struct timespec{
time_t tv_sec; /*seconds*/ 秒
long tv_nsec; /*nanosecondes*/ 纳秒
唤醒至少一个阻塞在条件变量上的线程
pthread_cond_signal(pthread_cond_t* cond);
参数: cond传入一个已经初始好的条件变量地址
唤醒全部阻塞在条件变量上的线程
pthread_cond_broadcast(pthread_cond_t* cond);
参数: cond传入一个已经初始好的条件变量地址
案例:
程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
看如下示例,使用条件变量模拟生产者、消费者问题:
#include#include #include #include //链表节点结构体 struct msg{ struct msg* next; //指针域 int num; //数据域 }; struct msg* head; //全局头指针 pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; void* consumer(void* args){ struct msg* mp = NULL; //创建一个节点指针 for(;;){ //无限循环 pthread_mutex_lock(&lock); //加锁 //?为何为while不为if, //如果消费者或其它线程提早调用了pthread_cond_signal,而没有产生数据 //而此时头指针还是为空, if会造成程序崩溃 while(NULL == head){ //头指针为空,说明没有数据 pthread_cond_wait(&has_product, &lock); } mp = head; //获取数据节点 head = mp->next; //模拟消费一个产品 pthread_mutex_unlock(&lock); printf("-Consume --%d\n", mp->num); //打屏提示 free(mp); //释放 sleep(rand() % 5); //等待一段时间 } } void* producer(void* args){ struct msg* mp = NULL; for(;;){ //生产一个产品 mp = (struct msg*)malloc(sizeof(struct msg)); mp->num = rand() % 1000 + 1; //1-1000之间 printf("-Produce ---%d\n", mp->num); pthread_mutex_lock(&lock); //加锁 mp->next = head; head = mp; //头插法加入产品链表 pthread_mutex_unlock(&lock); //解锁 //将等待在该条件变量的线程唤醒 pthread_cond_signal(&has_product); sleep(rand()%5); //等待 } } int main(int argc, char* argv[]){ pthread_t pid, cid; srand(time(NULL)); //随机数种子 pthread_create(&pid, NULL, producer, NULL); //创建一个生产者线程 pthread_create(&cid, NULL, consumer, NULL); //创建一个消费者线程 pthread_join(pid, NULL);//等待消费者线程结束 pthread_join(cid, NULL);//等待生产者线程结束 return 0; }
运行结果: