条件变量
唤醒再次获取互斥锁重新评估条件
主要应用函数
pthread_cond_init 函数。
pthread_cond_destroy函数。
pthread_cond_wait 函数。
pthread_cond_timedwait 函数。
pthread_cond_signal 函数。
pthread_cond_broadcast 函数。
以上6个函数的返回值都是,成功返回0,失败直接返回错误号
pthread_cond_t类型用于定义条件变量。
pthread_cond_t cond;
条件变量的用法
创建和注销
条件变量和互斥锁一样,都有静态动态两种创建方式,静态方式使用PTHREAD_COND_INITIALIZER
常量,如下:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
动态方式调用pthread_cond_init()
函数,定义如下:
int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
//成功返回0,失败返回错误码.
尽管POSIX标准中为条件变量定义了属性,但在LinuxThreads中没有实现,因此cond_attr
值通常为NULL,且被忽略。
注销一个条件变量需要调用pthread_cond_destroy()
函数,只有在没有线程在该条件变量上等待的时候才能注销这个条件变量,否则返回EBUSY。因为Linux实现的条件变量没有分配什么资源,所以注销动作只包括检查是否有等待线程。
定义如下:
int pthread_cond_destroy(pthread_cond_t *cond) ;
//成功返回0,失败返回错误码.
等待和唤醒
两种等待方式,无条件等待pthread_cond_wait()
和计时等待pthread_cond_timedwait()
。接口为:
int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,const struct timespec *abstime);
//成功返回0,失败返回错误码.
其中计时等待方式如果在给定时刻前条件没有满足,则返回ETIMEOUT,结束等待,其中abstime以与time()系统调用相同意义的绝对时间形式出现,0表示格林尼治时间1970年1月1日0时0分0秒。
无论哪种等待方式,都必须和一个互斥锁配合,以防止多个线程同时请求pthread_cond_wait()
(或pthread_cond_timedwait()
)的竞争条件(Race Condition)。mutex互斥锁必须是普通锁或者适应锁,且在调用pthread_cond_wait()
前必须由本线程加锁,而在更新条件等待队列以前,mutex保持锁定状态,并在线程挂起进入等待前解锁。在条件满足从而离开pthread_cond_wait()
之前,mutex将被重新加锁,以与进入pthread_cond_wait()
前的加锁动作对应。
pthread_cond_wait()
函数的返回并不意味着条件的值一定发生了变化,必须重新检查条件的值。
阻塞在条件变量上的线程被唤醒以后,直到pthread_cond_wait()函数返回之前条件的值都有可能发生变化。所以函数返回以后,在锁定相应的互斥锁之前,必须重新测试条件值。最好的测试方法是循环调用pthread_cond_wait函数,并把满足条件的表达式置为循环的终止条件。如:
pthread_mutex_lock();
while (condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
阻塞在同一个条件变量上的不同线程被释放的次序是不一定的。
注意:pthread_cond_wait()函数是退出点,如果在调用这个函数时,已有一个挂起的退出请求,且线程允许退出,这个线程将被终止并开始执行善后处理函数,而这时和条件变量相关的互斥锁仍将处在锁定状态。
唤醒
唤醒条件有两种形式,pthread_cond_signal()
唤醒一个等待该条件的线程,存在多个等待线程时按入队顺序唤醒其中一个;而pthread_cond_broadcast()
则唤醒所有等待线程。
int pthread_cond_signal(pthread_cond_t *cptr);
int pthread_cond_broadcast (pthread_cond_t * cptr);
//成功返回0,失败返回错误码.
必须在互斥锁的保护下使用相应的条件变量。否则对条件变量的解锁有可能发生在锁定条件变量之前,从而造成死锁。
唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。
如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()
将没有作用。
由于pthread_cond_broadcast函数唤醒所有阻塞在某个条件变量上的线程,这些线程被唤醒后将再次竞争相应的互斥锁,所以必须小心使用pthread_cond_broadcast
函数。
唤醒丢失问题
在线程未获得相应的互斥锁时调用pthread_cond_signal
或pthread_cond_broadcast
函数可能会引起唤醒丢失问题。
唤醒丢失往往会在下面的情况下发生:
一个线程调用pthread_cond_signal或pthread_cond_broadcast函数;
另一个线程正处在测试条件变量和调用pthread_cond_wait函数之间;
没有线程正在处在阻塞等待的状态下。
条件变量的使用可以分为两部分:
等待线程
使用pthread_cond_wait前要先加锁;
pthread_cond_wait内部会解锁,然后等待条件变量被其它线程激活;
pthread_cond_wait被激活后会再自动加锁;
激活线程:
加锁(和等待线程用同一个锁);
pthread_cond_signal发送信号;
解锁;
激活线程的上面三个操作在运行时间上都在等待线程的pthread_cond_wait函数内部。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count = 0;
void *decrement_count(void *arg)
{
pthread_mutex_lock(&count_lock);
printf("decrement_count get count_lock/n");
while(count == 0)
{
printf("decrement_count count == 0 /n");
printf("decrement_count before cond_wait /n");
pthread_cond_wait(&count_nonzero, &count_lock);
printf("decrement_count after cond_wait /n");
}
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
void *increment_count(void *arg)
{
pthread_mutex_lock(&count_lock);
printf("increment_count get count_lock /n");
if(count == 0)
{
printf("increment_count before cond_signal /n");
pthread_cond_signal(&count_nonzero);
printf("increment_count after cond_signal /n");
}
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
int main()
{
pthread_t tid1, tid2;
pthread_mutex_init(&count_lock, NULL);
pthread_cond_init(&count_nonzero, NULL);
pthread_create(&tid1, NULL, decrement_count, NULL);
sleep(2);
pthread_create(&tid2, NULL, increment_count, NULL);
sleep(10);
pthread_exit(0);
return 0;
}
运行结果:
decrement_count get count_lock
decrement_count count == 0
decrement_count before cond_wait
increment_count get count_lock
increment_count before cond_signal
increment_count after cond_signal
decrement_count after cond_wait
例程:生产-消费者模型代码
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
/*链表作为公享数据,需被互斥量保护*/
#define SIZE 100
int repository[SIZE]={0};//定义仓库大小,并初始化为0
/*静态初始化一个条件变量和一个互斥量*/
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int empty_repository(int arr[])
{
int i,sum=0;
for(i=0;i<SIZE;i++)
{
sum += arr[i];
}
return sum;
}
int fill_repository(int arr[])
{
int i,sum;
for(i=0;i<SIZE;i++)
{
if(arr[i]==0)
{
arr[i]= rand() % 1000 + 1;
printf("-------fill a[%d]=%d\n",i,arr[i]);
return arr[i];
}
}
return -1;
}
int del_repository(int arr[])
{
int i,temp;
for(i=SIZE;i>=0;i--)
{
if(arr[i]!=0)
{
temp=arr[i];
printf("-------del a[%d]=%d\n",i,arr[i]);
arr[i]= 0;
return temp;
}
}
return -1;
}
void *consumer(void *p)
{
for (;;)
{
int ret=pthread_mutex_lock(&lock);
if(ret !=0)
{
printf("consumer mutex_lock err:%s\n",strerror(ret));
}
while (!empty_repository(repository))
{
//如果条件不满足,释放锁,并阻塞在此处
//如果条件满足,重新加锁,并解除阻塞,进行循环条件判断
printf("cond_wait test mask\n");
pthread_cond_wait(&has_product, &lock);
}
//模拟消费掉一个产品
ret=del_repository(repository);
if(ret==-1)
{
printf("del_repository() err\n");
pthread_exit(NULL);
}
pthread_mutex_unlock(&lock);
printf("-Consume %lu---Produce id=%d\n", pthread_self(), ret);
sleep(rand() % 5);
}
}
void *producer(void *p)
{
for (; ;)
{ //sleep(5),为验证consumer线程中的,pthread_cond_wait()会进行解锁+阻塞功能
sleep(5);
//生产者拿锁成功,再生产产品
int ret=pthread_mutex_lock(&lock);
if(ret !=0)
{
printf("producer mutex_lock() err:%s\n",strerror(ret));
pthread_mutex_unlock(&lock);
break;//跳出循环
}
//模拟生产一个产品
ret=fill_repository(repository);
if(ret==-1)
{
printf("fill_repository() err\n");
pthread_mutex_unlock(&lock);
break;
// pthread_exit(NULL);//生产仓库满后,无法继续生产,退出生产线程
}
pthread_mutex_unlock(&lock);
printf("-producer %lu---Produce id=%d\n", pthread_self(), ret);
pthread_cond_signal(&has_product); //条件满足了,通知等待条件变量has_product的
线程
usleep(100000);
}
}
int main(int argc, char *argv[])
{
pthread_t tid01, tid02,tid03;
srand(time(NULL));
pthread_create(&tid01, NULL, producer, NULL);
pthread_create(&tid02, NULL, consumer, NULL);
// pthread_create(&tid03, NULL, consumer, NULL);
pthread_join(tid01, NULL);
pthread_join(tid02, NULL);
// pthread_join(tid03, NULL);
return 0;
}
信号量
信号量,是相对折中的一-种处理方式,既能保证同步,数据不混乱,又能提高线程并发。
int sem_init ( sem_t * sem , int pshared , unsigned int vale )参 1 : sem 已定义的信号量参 2 : pshared 0 用于线程间同步1 用于进程间同步参 3 : vale N 值,指定同时能够访问的线程数
int sen_destroy ( sem_t * sem );
int sem_wait ( sen_t * sem );
sem_post函数
信号量加加操作(类似解锁)
int sem_post(sen_t *sem);
int sem_timedwait ( sem_t * sem , const struct timespec * abs_timeout );参 2 : abs_timeout 采用的是绝对时间( 1970 年 1 月 1 日 0 时 0 分 0 秒)。比如定时 1 秒 :time_t cur = time ( NULL ); 获取当前时间struct timespec t ;定义 timespec 结构体变量 tt . tv_sec = cur + 1 ; 定时 1 秒。t . tv_nsec = t . tv_sec + 100 ;sem_timedwait ( & sem , & t ); 传参
例程:生产-消费者模型,使用信号量
#include <stdlib.h>
#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()%1);
}
}
void *consumer(void *arg)
{
int i=0;
while (1)
{
sem_wait(&product_number);//消费者将产品数--,为则阻塞等待
printf("-Consume---%d\n" , queue[i]);
queue[i] = 0;//消费一个产品
sem_post(&blank_number);//消费掉以后,将空格子数++
i = (i+1) % NUM;
sleep(rand( )%3);
}
}
int main(int argc, char *argv[])
{
pthread_t pid, cid;
sem_init(&blank_number, 0, NUM); //初始化线程间共享-0,空格子信号量为5,
sem_init(&product_number, 0,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;
}