【Linux】深入理解线程(线程同步、互斥量mutex、死锁、读写锁、条件变量、信号量)
2017-12-30 21:20
609 查看
一、同步概念
1、线程同步:
同步即协同步调,按预定的先后次序运行。
线程同步,只一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时,其他线程为保证数据一致性,不能调用该功能。
举例 1 :银行存款 5000。柜台,折:取 3000 ;提款机,卡:取 3000。 剩余:2000。
举例 2 :内存中 100 字节,线程 T1 欲填入 全 1,线程 T2 欲填入 全 0 。但如果 T1 执行了 50 个字节失去 CPU,T2 执行,会将 T1 写过的内容覆盖。当 T1 再次获得
CPU 继续从失去 cpu 的位置向后写入 1 ,当执行结束,内存中的 100 字节,既不是全 1,也不是全0。
产生的现象叫做 “与时间有关的错误” 。为了避免这种数据混乱 ,线程需要同步。
“ 同步 ” 的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间、信号间等等都需要同步机制。
因此,所有 “ 多个控制流,共同操作一个共享资源 ”的情况下,都需要同步。
2、数据混乱的原因:
(1)、资源共享(独享资源则不会)
(2)、调度随机(意味着数据访问会出现随机)
(3)、线程间缺乏必要的同步机制
二、互斥量 mutex
Linux 提供了一把互斥锁 mutex( 也称为互斥量 )。
每个线程在对资源操作前都会尝试先加锁,成功加锁才能操作,操作结束解锁。
资源还是共享的,线程间也还是竞争的。
但通过 ” 锁 ” 就将资源的访问变成互斥操作,而后与时间有关的错误也就不会产生了。
注意:同一时刻,只有一个线程持有该锁。
当 A 线程对某个全局变量加锁访问,B 锁在访问前尝试加锁,拿不到锁,B阻塞。C 线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。
所以,互斥锁实际上是操作系统提供的一把 “ 建议锁 ”(右称 “ 协同锁 ”),建议程序中有多线程访问共享数据资源的时候使用该机制。但,并没有强制限定。
因此,即使有了 Mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。
1、主要应用函数:
pthread_mutex_init 函数
pthread_mutex_destory 函数
pthread_mutex_lock 函数
pthread_mutex_trylock 函数
pthread_mutex_unlock 函数
以上 5 个函数的返回值都是:成功:返回 0; 失败: 返回错误号
pthread_mutex_t 类型,其本质上是一个结构体。
pthread_mutex_t mutex; 变量 mutex 只有两种取值 1,0
2、pthread_mutex_init 函数
初始化一个互斥锁(互斥量)------> 初值可看做 1
参数 1 : 传出参数,调用时应传 &mutex
restrict 关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成,不能通过本指针以外的其他变量或指针修改。
参数 2 : 互斥量属性。是一个传入参数,通常传 NULL,选用默认属性(线程间共享)
注意:
(1)、静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了 static 关键字修饰),可以直接使用宏进行初始化。eg : pthread_mutex_t mutex =
PTHREAD_MUTEX_INITIALIZER;
(2)、动态初始化:局部变量应采用动态初始化。eg:pthread_mutex_init(&mutex);
3、pthread_mutex_destory 函数
销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
4、pthread_mutex_lock 函数
加锁。可理解为 mutex -- (或 -1)
int pthread_mutex_lock(pthread_mutex_t *mutex);
5、pthread_mutex_trylock 函数
尝试加锁。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
6、pthread_mutex_unlock 函数
解锁。可理解为 mutex++ (或 +1)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
7、加锁与解锁
lock 与 unlock:
lock 尝试加锁,如果锁不成功,线程阻塞,阻塞到持有互斥量的线程解锁为止。
unlock 主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞,先唤醒。
例如:T1 T2 T3 T4 使用一把 互斥锁。T1 加锁成功,其他线程均阻塞,直至 T1 解锁。T1 解锁后,T2,T3,T4均被唤醒,并自动再次尝试加锁。
lock 与 trylock:
lock 加锁失败会阻塞,等待锁释放。
trylock 加锁失败直接返回错误号(如:EBUSY),不阻塞。
小结:
在访问共享资源前加锁,访问结束后立即解锁。锁的 “ 粒度 ” 应越小越好。
互斥锁代码示例:
8、死锁
(1)、线程试图对一个线程 A 加锁两次。
(2)、线程 1 拥有 A 锁,请求获得 B 锁;线程 2 拥有 B 锁,请求获得 A 锁。
三、读写锁
与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。
1、读写锁状态:
(1)、读模式下加锁状态(读锁)
(2)、写模式下加锁状态(写锁)
(3)、不加锁状态
2、读写锁特性:
(1)、读写锁是 “ 写模式加锁 ” 时,解锁前,所有对该锁加锁的线程都会被阻塞。
(2)、读写锁是 “ 读模式加锁 ” 时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
(3)、读写锁是 “ 读模式加锁 ” 时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式请求。优先满足写模式锁。读
锁、写锁并行阻塞,写锁优先级高。
读写锁也叫共享 --- 独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式 锁住时,它是以独占模式锁住的 。写独占,读共享。
读写锁是适用于:对数据结构读的次数大于写的次数。
3、主要应用函数:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
以上 7 个函数的返回值: 成功返回 0 ,失败返回错误号。
pthread_rwlock_t 类型,用于定义一个读写锁变量。
pthread_rwlock_t rwlock;
4、pthread_rwlock_init 函数
初始化一把读写锁 。
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
餐数 2 :attr 表示读写锁属性,通常使用 默认属性,传 NULL 即可。
也可以使用静态初始化的方法,初始化条件变量:
5、pthread_rwlock_destroy 函数
销毁一个读写锁变量
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
6、读写锁示例程序:
四、条件变量
条件变量本身不是锁!但它可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个汇合的场所。
主要应用函数:
pthread_cond_init 函数
pthread_cond_destory 函数
pthread_cond_wait 函数
pthread_cond_timedwait 函数
pthread_cond_signal 函数
pthread_cond_broadcast 函数
以上 6 个函数的返回值都是:成功返回 0 ,失败直接返回错误号。
pthread_cond_t 类型 用于定义条件变量
pthread_cond_t cond;
1、pthread_cond_init 函数
初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数 2 :attr 表示条件变量属性,通常为默认值,传 NULL即可,也可以使用静态初始化的方法,初始化条件变量。 pthread_cond_t cond
= PTHREAD_COND_INITIALIZER;
2、pthread_cond_destroy 函数
销毁一个调件变量
int pthread_cond_destroy(pthread_cond_t *cond);
3、pthread_cond_wait 函数
阻塞一个条件变量。
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
函数作用:
(1)、阻塞等待条件变量 cond (参数1)满足
(2)、释放已经掌握的互斥锁(解锁互斥量),相当于 pthread_mutex_unlcok(&mutex);
(1),(2)两步为原子操作
(3)、当被唤醒,pthread_cond_wait 返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex_lock(&mutex);
4、pthread_cond_timedwait 函数
限时等待一个条件变量。
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
参数 3 :查看 man sem_timedwait 函数,查看 struct timespec 结构体。
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
形参abstime : 绝对时间。
如:time(NULL) 返回的就是绝对时间。而 alarm(1)是相对时间,相对当前时间定时 1 秒。
struct timespec t = {1,0};
pthread_cond_timedwait(&cond,&mutex,&t); 只能定时到 1970 年 1 月 1 日 00:00:01秒(早已过去)
正确的用法 :
time_t cur = time(NULL);
struct timespec_t; 定义 timespec 结构体变量 t
t.tv_sec = cur + 1; 定时 1 秒
pthread_cond_timedwait(&cond,&mutex,&t); 传参
5、pthread_cond_signal 函数
唤醒至少阻赛在条件变量上的线程。
int pthread_cond_broadcast(pthread_cond_t *cond);
6、pthread_cond_broadcast 函数
唤醒至少阻赛在条件变量上的线程。
int pthread_cond_signal(pthread_cond_t *cond);
7、生产者消费者模型
线程同步的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者的行为,一个模拟消费者的行
为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
8、条件变量的优点:
相较于 Mutex 而言,条件变量可以减少竞争。
如果直接使用 mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争资源是无意义的。
有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序的效率。
五、信号量
进化版的互斥锁(1 ---> N )
由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的某一部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据锁住。这样虽然达到了多
线程操做共享数据时,保证数据的正确性的目的,却无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异。
信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。
主要应用函数:
sem_init 函数
sem_destroy函数
sem_wait 函数
sem_trywait 函数
sem_timewait 函数
sem_post 函数
以上 6 个函数的返回值都是: 成功返回 0,失败返回 -1 ,同时设置 errrno。(注意,它们没有 pthread 前缀)
sem_t 类型,本质上仍是结构体
sem_t sem; 规定信号量 sem 不能 <0 。头文件 <semaphore.h>
1、信号量的基本操作
sem_wait : (a)、信号量大于 0,则信号量 -- ( 类比 pthread_mutex_lock(&lock) )
| (b)、信号量等于 0 ,造成线程阻塞
|
对应
|
|
sem_post : 将信号量 ++,同时唤醒阻塞在信号量上的线程 ( 类比 pthread_mutex_unlock )
但,由于 sem_t 的实现对用户隐藏,所以所谓的 ++,-- 操作只能通过函数来实现,而不能直接 ++、-- 符号。
信号量的初值,决定了占用信号量的线程个数。
2、sem_init 函数
初始化一个信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数 1 : sem 信号量
参数 2: :pshared 取 0 用于线程间;取非 0 ( 一般为 1 )用于进程间
参数 3 :value 指定信号量初值
3、sem_destroy函数
销毁一个信号量
int sem_destroy(sem_t *sem);
4、sem_wait 函数
给信号量加锁 --
int sem_wait(sem_t *sem);
5、sem_post 函数
给信号量解锁 ++
int sem_post(sem_t *sem);
6、sem_trywait 函数
尝试对信号量加锁 -- ( 与 sem_wait 的区别类比 lock 与 trylock)
int sem_trywait(sem_t *sem);
7、sem_timedwait 函数
限时阻塞信号量
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
8、生产者消费者信号量模型
1、线程同步:
同步即协同步调,按预定的先后次序运行。
线程同步,只一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时,其他线程为保证数据一致性,不能调用该功能。
举例 1 :银行存款 5000。柜台,折:取 3000 ;提款机,卡:取 3000。 剩余:2000。
举例 2 :内存中 100 字节,线程 T1 欲填入 全 1,线程 T2 欲填入 全 0 。但如果 T1 执行了 50 个字节失去 CPU,T2 执行,会将 T1 写过的内容覆盖。当 T1 再次获得
CPU 继续从失去 cpu 的位置向后写入 1 ,当执行结束,内存中的 100 字节,既不是全 1,也不是全0。
产生的现象叫做 “与时间有关的错误” 。为了避免这种数据混乱 ,线程需要同步。
“ 同步 ” 的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间、信号间等等都需要同步机制。
因此,所有 “ 多个控制流,共同操作一个共享资源 ”的情况下,都需要同步。
2、数据混乱的原因:
(1)、资源共享(独享资源则不会)
(2)、调度随机(意味着数据访问会出现随机)
(3)、线程间缺乏必要的同步机制
二、互斥量 mutex
Linux 提供了一把互斥锁 mutex( 也称为互斥量 )。
每个线程在对资源操作前都会尝试先加锁,成功加锁才能操作,操作结束解锁。
资源还是共享的,线程间也还是竞争的。
但通过 ” 锁 ” 就将资源的访问变成互斥操作,而后与时间有关的错误也就不会产生了。
注意:同一时刻,只有一个线程持有该锁。
当 A 线程对某个全局变量加锁访问,B 锁在访问前尝试加锁,拿不到锁,B阻塞。C 线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。
所以,互斥锁实际上是操作系统提供的一把 “ 建议锁 ”(右称 “ 协同锁 ”),建议程序中有多线程访问共享数据资源的时候使用该机制。但,并没有强制限定。
因此,即使有了 Mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。
1、主要应用函数:
pthread_mutex_init 函数
pthread_mutex_destory 函数
pthread_mutex_lock 函数
pthread_mutex_trylock 函数
pthread_mutex_unlock 函数
以上 5 个函数的返回值都是:成功:返回 0; 失败: 返回错误号
pthread_mutex_t 类型,其本质上是一个结构体。
pthread_mutex_t mutex; 变量 mutex 只有两种取值 1,0
2、pthread_mutex_init 函数
初始化一个互斥锁(互斥量)------> 初值可看做 1
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, // 给 mutex 变量动态初始化 const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 给 mutex 变量静态初始化
参数 1 : 传出参数,调用时应传 &mutex
restrict 关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成,不能通过本指针以外的其他变量或指针修改。
参数 2 : 互斥量属性。是一个传入参数,通常传 NULL,选用默认属性(线程间共享)
注意:
(1)、静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了 static 关键字修饰),可以直接使用宏进行初始化。eg : pthread_mutex_t mutex =
PTHREAD_MUTEX_INITIALIZER;
(2)、动态初始化:局部变量应采用动态初始化。eg:pthread_mutex_init(&mutex);
3、pthread_mutex_destory 函数
销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
4、pthread_mutex_lock 函数
加锁。可理解为 mutex -- (或 -1)
int pthread_mutex_lock(pthread_mutex_t *mutex);
5、pthread_mutex_trylock 函数
尝试加锁。
int pthread_mutex_trylock(pthread_mutex_t *mutex);
6、pthread_mutex_unlock 函数
解锁。可理解为 mutex++ (或 +1)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
7、加锁与解锁
lock 与 unlock:
lock 尝试加锁,如果锁不成功,线程阻塞,阻塞到持有互斥量的线程解锁为止。
unlock 主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞,先唤醒。
例如:T1 T2 T3 T4 使用一把 互斥锁。T1 加锁成功,其他线程均阻塞,直至 T1 解锁。T1 解锁后,T2,T3,T4均被唤醒,并自动再次尝试加锁。
lock 与 trylock:
lock 加锁失败会阻塞,等待锁释放。
trylock 加锁失败直接返回错误号(如:EBUSY),不阻塞。
小结:
在访问共享资源前加锁,访问结束后立即解锁。锁的 “ 粒度 ” 应越小越好。
互斥锁代码示例:
#include<unistd.h> #include<pthread.h> #include<stdlib.h> #include<string.h> #include<stdio.h> pthread_mutex_t mutex; // 定义锁 void *thd_func(void *arg) { srand(time(NULL)); while(1) { pthread_mutex_lock(&mutex); printf("hello "); sleep(rand()%3); printf("world\n"); // sleep(rand()%3); pthread_mutex_unlock(&mutex); sleep(rand()%3); } return NULL; } int main(void) { pthread_t tid; int flag = 5; srand(time(NULL)); pthread_mutex_init(&mutex,NULL); // 初始化锁 pthread_create(&tid,NULL,thd_func,NULL); while(flag--) { pthread_mutex_lock(&mutex); // 加锁 printf("HELLO "); sleep(rand()%3); printf("WORLD\n"); // sleep(rand()%3); pthread_mutex_unlock(&mutex); // 解锁 sleep(rand()%3); } pthread_cancel(tid); // 杀死线程 pthread_join(tid,NULL); // 回收子线程 pthread_mutex_destroy(&mutex); // 销毁锁 return 0; }
8、死锁
(1)、线程试图对一个线程 A 加锁两次。
(2)、线程 1 拥有 A 锁,请求获得 B 锁;线程 2 拥有 B 锁,请求获得 A 锁。
三、读写锁
与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。
1、读写锁状态:
(1)、读模式下加锁状态(读锁)
(2)、写模式下加锁状态(写锁)
(3)、不加锁状态
2、读写锁特性:
(1)、读写锁是 “ 写模式加锁 ” 时,解锁前,所有对该锁加锁的线程都会被阻塞。
(2)、读写锁是 “ 读模式加锁 ” 时,如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。
(3)、读写锁是 “ 读模式加锁 ” 时,既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式请求。优先满足写模式锁。读
锁、写锁并行阻塞,写锁优先级高。
读写锁也叫共享 --- 独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式 锁住时,它是以独占模式锁住的 。写独占,读共享。
读写锁是适用于:对数据结构读的次数大于写的次数。
3、主要应用函数:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
以上 7 个函数的返回值: 成功返回 0 ,失败返回错误号。
pthread_rwlock_t 类型,用于定义一个读写锁变量。
pthread_rwlock_t rwlock;
4、pthread_rwlock_init 函数
初始化一把读写锁 。
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
餐数 2 :attr 表示读写锁属性,通常使用 默认属性,传 NULL 即可。
也可以使用静态初始化的方法,初始化条件变量:
5、pthread_rwlock_destroy 函数
销毁一个读写锁变量
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
6、读写锁示例程序:
/* 3 个线程不定时“写”全局资源, 5 个线程不定时“读” 同一全局资源 */ #include<unistd.h> #include<pthread.h> #include<stdio.h> int counter; // 全局资源 pthread_rwlock_t rwlock; // 读写锁变量 void thd_write(void *arg) { int t; int i = (int) arg; while(1) { t = counter; usleep(1000); // 睡眠 1000 ms // 设置写锁 pthread_rwlock_wrlock(&rwlock); printf("========wirte %d: %lu: counter=%d ++counter=%d\n",i,pthread_self(),t,++counter); // 设置解锁 pthread_rwlock_unlock(&rwlock); // 睡眠 5000 ms usleep(5000); } return NULL; } void thd_read(void *arg) { int i = (int)arg; while(1) { // 设置读锁 pthread_rwlock_rdlock(&rwlock); printf("----------------------------read %d: %lu: %d\n",i,pthread_self(),counter); // 设置读锁解锁 pthread_rwlock_unlock(&rwlock); // 睡眠 900 ms usleep(900); } return NULL; } int main(void) { pthread_t tid[8]; int i; // 初始化读写锁 pthread_rwlock_init(&rwlock,NULL); // 循环创建 3 个线程进行写操作 for( i=0;i<3;i++ ) pthread_create(&tid[i],NULL,thd_write,(void*)i); // 循环创建 5 个线程进行读操作 for( i=0;i<5;i++ ) pthread_create(&tid[i],NULL,thd_read,(void*)i); // 回收 8 个线程资源 for( i=0;i<8;i++ ) pthread_join(tid[i],NULL); // 释放读写锁资源 pthread_rwlock_destroy(&rwlock); return 0; }
四、条件变量
条件变量本身不是锁!但它可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个汇合的场所。
主要应用函数:
pthread_cond_init 函数
pthread_cond_destory 函数
pthread_cond_wait 函数
pthread_cond_timedwait 函数
pthread_cond_signal 函数
pthread_cond_broadcast 函数
以上 6 个函数的返回值都是:成功返回 0 ,失败直接返回错误号。
pthread_cond_t 类型 用于定义条件变量
pthread_cond_t cond;
1、pthread_cond_init 函数
初始化一个条件变量
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
参数 2 :attr 表示条件变量属性,通常为默认值,传 NULL即可,也可以使用静态初始化的方法,初始化条件变量。 pthread_cond_t cond
= PTHREAD_COND_INITIALIZER;
2、pthread_cond_destroy 函数
销毁一个调件变量
int pthread_cond_destroy(pthread_cond_t *cond);
3、pthread_cond_wait 函数
阻塞一个条件变量。
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
函数作用:
(1)、阻塞等待条件变量 cond (参数1)满足
(2)、释放已经掌握的互斥锁(解锁互斥量),相当于 pthread_mutex_unlcok(&mutex);
(1),(2)两步为原子操作
(3)、当被唤醒,pthread_cond_wait 返回时,解除阻塞并重新申请获取互斥锁 pthread_mutex_lock(&mutex);
4、pthread_cond_timedwait 函数
限时等待一个条件变量。
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const struct timespec *restrict abstime);
参数 3 :查看 man sem_timedwait 函数,查看 struct timespec 结构体。
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
形参abstime : 绝对时间。
如:time(NULL) 返回的就是绝对时间。而 alarm(1)是相对时间,相对当前时间定时 1 秒。
struct timespec t = {1,0};
pthread_cond_timedwait(&cond,&mutex,&t); 只能定时到 1970 年 1 月 1 日 00:00:01秒(早已过去)
正确的用法 :
time_t cur = time(NULL);
struct timespec_t; 定义 timespec 结构体变量 t
t.tv_sec = cur + 1; 定时 1 秒
pthread_cond_timedwait(&cond,&mutex,&t); 传参
5、pthread_cond_signal 函数
唤醒至少阻赛在条件变量上的线程。
int pthread_cond_broadcast(pthread_cond_t *cond);
6、pthread_cond_broadcast 函数
唤醒至少阻赛在条件变量上的线程。
int pthread_cond_signal(pthread_cond_t *cond);
7、生产者消费者模型
线程同步的案例即为生产者消费者模型,而借助条件变量来实现这一模型,是比较常见的一种方法。假定有两个线程,一个模拟生产者的行为,一个模拟消费者的行
为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
/* 借助条件变量模拟 生产者 - 消费者 问题 */ #include<unistd.h> #include<pthread.h> #include<stdio.h> #include<stdlib.h> // 链表作为共享数据,需要被互斥量保护 struct msg { struct msg* next; int num; }; struct msg* head; struct msg* mp; // 静态初始化一个条件变量和一个互斥变量 pthread_cond_t has_product = PTHREAD_COND_INITIALIZER; pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // 消费者线程的操作 void *consumer(void* p) { for( ; ; ) { // 加上互斥锁 pthread_mutex_lock(&lock); while( head == NULL ) // 头指针为空,说明没有节点 { 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 *producter(void *p) { for( ; ; ) { mp = (struct msg*)malloc(sizeof(struct msg)); mp->num = rand() % 1000 + 1; // 模拟生产一个产品 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 tid,cid; srand(time(NULL)); pthread_create(&tid,NULL,producter,NULL); pthread_create(&cid,NULL,consumer,NULL); pthread_join(tid,NULL); pthread_join(cid,NULL); sleep(rand() % 5); return 0; }
8、条件变量的优点:
相较于 Mutex 而言,条件变量可以减少竞争。
如果直接使用 mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争资源是无意义的。
有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序的效率。
五、信号量
进化版的互斥锁(1 ---> N )
由于互斥锁的粒度比较大,如果我们希望在多个线程间对某一对象的某一部分数据进行共享,使用互斥锁是没有办法实现的,只能将整个数据锁住。这样虽然达到了多
线程操做共享数据时,保证数据的正确性的目的,却无形中导致线程的并发性下降。线程从并行执行,变成了串行执行。与直接使用单进程无异。
信号量,是相对折中的一种处理方式,既能保证同步,数据不混乱,又能提高线程并发。
主要应用函数:
sem_init 函数
sem_destroy函数
sem_wait 函数
sem_trywait 函数
sem_timewait 函数
sem_post 函数
以上 6 个函数的返回值都是: 成功返回 0,失败返回 -1 ,同时设置 errrno。(注意,它们没有 pthread 前缀)
sem_t 类型,本质上仍是结构体
sem_t sem; 规定信号量 sem 不能 <0 。头文件 <semaphore.h>
1、信号量的基本操作
sem_wait : (a)、信号量大于 0,则信号量 -- ( 类比 pthread_mutex_lock(&lock) )
| (b)、信号量等于 0 ,造成线程阻塞
|
对应
|
|
sem_post : 将信号量 ++,同时唤醒阻塞在信号量上的线程 ( 类比 pthread_mutex_unlock )
但,由于 sem_t 的实现对用户隐藏,所以所谓的 ++,-- 操作只能通过函数来实现,而不能直接 ++、-- 符号。
信号量的初值,决定了占用信号量的线程个数。
2、sem_init 函数
初始化一个信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数 1 : sem 信号量
参数 2: :pshared 取 0 用于线程间;取非 0 ( 一般为 1 )用于进程间
参数 3 :value 指定信号量初值
3、sem_destroy函数
销毁一个信号量
int sem_destroy(sem_t *sem);
4、sem_wait 函数
给信号量加锁 --
int sem_wait(sem_t *sem);
5、sem_post 函数
给信号量解锁 ++
int sem_post(sem_t *sem);
6、sem_trywait 函数
尝试对信号量加锁 -- ( 与 sem_wait 的区别类比 lock 与 trylock)
int sem_trywait(sem_t *sem);
7、sem_timedwait 函数
限时阻塞信号量
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
8、生产者消费者信号量模型
// 信号量实现 生产者 消费者问题 #include<unistd.h> #include<pthread.h> #include<stdio.h> #include<semaphore.h> #define NUM 5 int queue[NUM]; // 全局数组实现环形队列 sem_t blank_number,product_number; // 空格子信号量,产品信号量 // 生产者 void *producer(void *arg) { int i = 0; while(1) { sem_wait(&blank_number); // 生产者将格子数 -- , 为 0 则阻塞等待 queue[i] = rand() % 1000 + 1; // 生产一个产品 printf("-------Produce---%d\n",queue[i]); sem_post(&product_number); // 将产品数 ++ i = (i+1) % NUM; // 借助下标实现环形 sleep(rand() % 3 ); } } // 消费者 void *consumer(void *arg) { int i = 0; while(1) { sem_wait(&product_number); // 消费者将产品数 -- ,为 0 则阻塞 printf("-Consumer---%d\n",queue[i]); queue[i] = 0; // 消费掉一个产品 sem_post(&blank_number); // 消费掉产品以后将空格子数 ++ i = (i+1) % NUM; sleep(rand()%3); } } int main(void) { pthread_t pid,cid; sem_init(&blank_number,0,NUM); // 初始化空格子信号量为 5 sem_init(&product_number,0,0); // 产品数 为 0 pthread_create(&pid,NULL,producer,NULL); pthread_create(&cid,NULL,consumer,NULL); pthread_join(pid,NULL); pthread_join(cid,NULL); sem_destroy(&blank_number); sem_destroy(&product_number); return 0; }
相关文章推荐
- 【Linux】深入理解线程(线程同步、互斥量mutex、死锁、读写锁、条件变量、信号量)
- Linux的线程同步对象:互斥量Mutex,读写锁,条件变量
- Linux的线程同步对象:互斥量Mutex,读写锁,条件变量
- linux c++多线程 线程私有数据 互斥量 条件变量 信号量 读写锁 自旋锁 屏障
- linux中的互斥锁--mutex,条件变量,信号量,读写锁
- 【Linux】线程总结:线程同步 -互斥锁,条件变量,信号量实现多生产者多消费者模型
- linux学习---线程同步(互斥量,信号量,条件量)线程属性
- 线程同步机制:互斥量、信号量、读写锁、条件变量
- linux下多线程同步机制之信号量、互斥量、读写锁、条件变量
- (四十三)线程——线程同步(互斥锁、读写锁、条件变量、信号量)
- 线程同步机制:互斥量、信号量、读写锁、条件变量
- 线程同步的方式——Mutex(互斥量)、 Condition variable(条件变量)和Semaphore(信号量)
- 线程同步(信号量,互斥,条件变量) 分类: linux应用程序 2008-04-08 09:43 2587人阅读 评论(1) 收藏 举报 进行多线程编程,最头疼的就是那些共享的数据。因为你无法知道哪个线程会在哪个时候对它进行操作,你也无法
- 深入理解linux互斥锁(mutex)
- Linux线程同步: 互斥量(mutex)
- Linux下面的线程锁,条件变量以及信号量的使用
- [Linux 线程]深入理解pthread_cond_wait、pthread_cond_signal
- Linux 线程同步机制:互斥、读写锁、条件变量
- 【Linux】Mutex互斥量线程同步的例子
- 深入理解linux互斥锁(mutex)