Linux系统编程56 线程 - 条件变量

为什么要有条件变量:为了弥补互斥锁的不足

互斥锁的不足:线程只有持锁执行,无锁阻塞(pthread_mutex_lock)两种状态,而如果某个线程是 根据 共享数据的某个变化是否发生 作为依据 来做其他操作。那么如果用互斥锁来实现的话,那么该线程会重复如下操作:

申请持锁成功
	判断 (共享数据的某个变化是否发生)
		如果有发生,执行其他操作
	如果没发生,解锁,(加sleep()会影响效率)并重新申请持锁
申请持锁失败 阻塞(pthread_mutex_lock) 等待持锁

如果 共享数据的某个变化 需要比较久才发生,那么 该线程就会反复执行上面的操作,造成CPU处理时间的浪费。有人会说可以在改伪代码中加 sleep()节省CPU处理时间,但是这样的后果是,不能保证该进程以最快的速度 查到 共享数据的某个变化,影响效率。即类似情况,互斥锁,会有时间和效率的问题。

所以 为了解决 上述时间和效率问题,引入 条件变量

条件变量
条件变量允许线程阻塞 并 等待其他线程给自己发送信号唤醒自己。兼顾CPU处理时间和效率,这样就弥补了互斥锁的不足。


相关函数:
pthread_cond_destroy()
pthread_cond_init()
pthread_cond_broadcast()
pthread_cond_signal()
pthread_cond_timedwait()
pthread_cond_wait()

pthread_cond_t 类型,条件变量类型


NAME
       pthread_cond_destroy, pthread_cond_init — destroy and initialize condition variables

SYNOPSIS
       #include <pthread.h>

//销毁
       int pthread_cond_destroy(pthread_cond_t *cond);

//动态初始化条件变量
       int pthread_cond_init(pthread_cond_t *restrict cond,
           const pthread_condattr_t *restrict attr);

//静态初始化条件变量
       pthread_cond_t cond = PTHREAD_COND_INITIALIZER;



NAME
       pthread_cond_broadcast, pthread_cond_signal — broadcast or signal a condition 广播或发出条件的信号

SYNOPSIS
       #include <pthread.h>

       //广播形式的唤醒,唤醒所有因 该条件变量cond而阻塞的线程
       int pthread_cond_broadcast(pthread_cond_t *cond);

       //唤醒 因为该条件变量cond而阻塞的所有线程中的任意一个线程
       int pthread_cond_signal(pthread_cond_t *cond);




NAME
       pthread_cond_timedwait, pthread_cond_wait — wait on a condition

SYNOPSIS
       #include <pthread.h>

//非阻塞等待条件变量变为真,超时等待
       int pthread_cond_timedwait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex,
           const struct timespec *restrict abstime);

//阻塞等待条件变量变为真,参数:条件变量,互斥量
       int pthread_cond_wait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex);

pthread_cond_wait() 用于阻塞线程 并 将线程挂载到 cond条件变量的 阻塞线程队列中,然后解锁,最后等待唤醒。被唤醒之后会抢锁,如果只有自己一个阻塞线程,则被唤醒后 直接持锁。

使用pthread_cond_wait方式如下:

1    pthread _mutex_lock(&mutex)

2    while或if(线程执行的条件是否成立)
          pthread_cond_wait(&cond, &mutex);

3    线程执行

4    pthread_mutex_unlock(&mutex);

问题一 : 为什么要先持锁

:条件变量是为了弥补互斥锁的不足,两者都是为了 同一时间只能有一个线程访问临界资源,所以必然要加锁。

问题二:pthread_cond_wait() 怎么防止 持锁阻塞,即如果在没有解锁的条件下进入阻塞状态,这一行的话,其他线程将无法访问临界资源。

扫描二维码关注公众号,回复: 12940098 查看本文章

:pthread_cond_wait() 内部的实现可以理解为,先将线程挂载到 阻塞队列中,然后解锁。所以在函数内部,已经解锁了。至于函数内部解锁的时机是很重要的,函数是在将 线程挂载到阻塞队列之后 才解锁。这样做的目的是,保证在将线程挂载到阻塞队列之前,其他线程无法改变临界资源。试想一下,如果先解锁,再挂载,会发生什么情况,如果解锁之后,其他线程拿到锁,并改变了临界资源,并发送信号 pthread_cond_signal(), 就会导致 本线程无法响应本次 临界资源的变化。

情景:
线程持锁,并判断条件是否成立,不成立的话,pthread_cond_wait() 阻塞线程 并 将线程挂载到 cond条件变量的 阻塞线程队列中,然后解锁,最后等待唤醒。pthread_cond_wait() 被唤醒之后会抢锁,如果只有自己一个阻塞线程,则被唤醒后 直接持锁, 并开始判断条件是否成立。。。。 成立的话 跳出循环,执行下一步。

实验1:
用 条件变量 通知法 实现 :规定时间内 四个线程 按顺序 分别不停的向终端 输出 a,b,c,d 每个线程输出一个字母

规范的写法:用条件变量实现 通知法

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>

#define THRNUM 4

static int num = 0;

static pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;

static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

static int next(int n)
{
	if(n+1 == THRNUM)
		return 0;
	return n+1;

}

static void *thr_add(void *p)
{
	int n = (int)p;//0 1 2 3
	int c = 'a' + (int)p;
	while(1)
	{
		pthread_mutex_lock(&mut);
		while(num != n)//判断是不是目标下一个线程
			pthread_cond_wait(&cond,&mut);//被唤醒的所有线程抢锁,抢到锁的线程 判断条件是否成立。。。
		write(1,&c,1);
		num = next(num);//准备唤醒下一个线程
		pthread_cond_broadcast(&cond);//惊群
		pthread_mutex_unlock(&mut);
	}
		

	pthread_exit(NULL);
} 

int main()
{
	pthread_t tid[THRNUM];
	int i,err;

	for(i = 0; i < THRNUM; i++)
	{
		err = pthread_create(tid+i,NULL,thr_add,(void *)i);
		if(err)
		{
			fprintf(stderr,"pthread_create(): %s\n",strerror(err));
			exit(1);
		}
	}

	//alarm(5);

	for(i = 0;i < THRNUM; i++)
	{
		pthread_join(tid[i],NULL);
	}

	pthread_mutex_destroy(&mut);
	pthread_cond_destroy(&cond);

	exit(0);
	
}

猜你喜欢

转载自blog.csdn.net/LinuxArmbiggod/article/details/114288246
今日推荐