开发使用多线程过程中, 不可避免的会出现多个线程同时操作同一块共享资源, 当操作全部为读时, 不会出现未知结果, 一旦当某个线程操作中有写操作时, 就会出现数据不同步的事件.
而出现数据混乱的原因:
资源共享(独享资源则不会)
调试随机(对数据的访问会出现竞争)
线程间缺少必要的同步机制
以上三点, 前两点不能被改变. 欲提高效率, 传递数据, 资源必须共享. 只要资源共享, 就一定会出现线程间资源竞争, 只要存在竞争关系, 数据就会出现混乱.
所以只能从第三点着手, 使多个线程在访问共享资源的时候, 出现互斥.
线程同步:
指在一定的时间里只允许某一个进程访问某个资源,而在此时间内,不允许其它线程对该资源进行操作.
线程的同步机制:
-
互斥量(互斥锁)
读写锁
条件变量(需要配合互斥量(互斥锁)来使用)
信号量
由于前章节介绍完了互斥量(互斥锁)和读写锁, 本次介绍条件变量
条件变量(需要配合互斥量(互斥锁)来使用):
条件变量本身不是锁! 但它可以造成阻塞.通常与互斥量(互斥锁)一起使用,给多线程提供一个会合的场所.给互斥量(互斥锁)一层包装.
条件变量主要用的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传入一个已经初始好的条件变量地址
案例:
程同步典型的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
看如下示例,使用条件变量模拟生产者、消费者问题:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
//链表节点结构体
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;
}
|
运行结果:
本文转自asd1123509133 51CTO博客,原文链接:http://blog.51cto.com/lisea/1790097,如需转载请自行联系原作者