03Linux下C语言锁的学习之条件变量

03Linux下C语言锁的学习之条件变量

1 为何要使用条件变量?

  • 通过上两篇对互斥锁和读写锁的学习,我们知道锁是用来保证共享数据的访问混乱,实现多线程同步。
    但是你有没有想过,当这个共享数据不是固定的一个,而是会变化的呢。何为变化,就是说不断有人生产数据,不断有人消费数据,也就是我们经典的生产者消费者模型。而非上两篇例子中共享数据为固定的一个全局共享变量。
    按照我们前两篇的做法,各个线程使用锁把这个装着共享数据的容器(size不断变化)锁住,然后不断轮询访问这个容器(假设为队列),即类似下面的代码:
while(1){
	pthread_mutex_lock(&mutex);
	//访问队列的数据
	pthread_mutex_unlock(&mutex);
}
  • 这样做的办法不是说不行,但是如果你每个线程都这样做,此时你使用top命令可以看到,你的这个进程占用的cpu高达100%甚至以上。非常浪费CPU,有人可能说睡眠一下就行了,确实睡眠会降低CPU很大的使用率,会降低到与平时正常水平,可是这样的话,你这还叫多线程处理吗?每个线程睡眠几秒,是非常耗时了。例如你登个游戏,每个画面等多几秒,你消费者还愿意使用这个产品吗?肯定不会。

引用别人博主的图片:
博主博客如下,这个博主写得也不错的:
C++多线程并发(三)—线程同步之条件变量

在这里插入图片描述

  • 所以此时条件变量就来了。条件变量就是干这个事情的,当没有共享数据消费的时候,它就阻塞等待别人的通知,不必while每秒轮询;当有数据消费的时候,别人就会通知他去消费,非常智能。
  • 条件变量一般是(也可以说必须)依赖着锁一起工作的。条件变量和锁它们两个都会造成阻塞,这一点非常重要,下面的函数pthread_cond_wait()将会讲到。

2 与条件变量相关的函数
注意,这些函数都是LinuxC下的函数,与Windows无关,并且和C++关系不大,因为C++自己有自己更先进的条件变量使用,在后续我的C++博客将会讲到。

1)pthread_cond_init函数

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
/*
	功能:初始化一个条件变量。
	参1:条件变量。一般放在全局。
	参2:条件变量属性。一般为NULL取默认属性。
	
	此外,也可以使用静态初始化的方法,与调用该函数一样:
	pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
*/

2)pthread_cond_destroy函数

int pthread_cond_destroy(pthread_cond_t *cond);
/*
	功能:销毁一个条件变量。
	参1:条件变量。
*/

3)pthread_cond_wait函数
这个函数非常重要,是锁和条件变量相关函数中最重要的函数。

int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
/*
	功能:简单说,就是阻塞等待一个条件变量。但是实际上它内部有三个功能:
	1)阻塞等待一个条件变量。
	2)解锁。也就是说,调用该函数之前必须上锁。(注意:第一第二步为原子操作,即这两个步骤是顺序连续执行的,不可能再这个线程执行一个另一个线程执行一个。)
	3)上锁。
	
	解释上面每一个步骤的作用:
	1)当线程没有被其它线程使用broadcast或者signal唤醒时,就会阻塞在条件变量。注意这里是条件变量。
	2)由于已经阻塞在条件变量了,所以必须释放锁让其它线程操作,例如生产者存放数据需要获取锁才行。
	3)当被唤醒的时候,该函数会重新上锁,以便恢复原来上锁的状态。并且确定有数据可以消费后,上锁的状态可以阻塞其它线程访问,防止数据混乱。这是锁的阻塞。与第一点条件变量的阻塞区分开来。
	也就是说,该函数拥有两次阻塞的功能。条件变量的阻塞是防止CPU占有率过高,锁的阻塞是防止数据访问混乱。

	参1:条件变量。
	参2:互斥锁。
*/

4)pthread_cond_timedwait函数

int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
/*
	功能:与上面的一样,但是多了一个最大等待时间参数。当超过这个时间的阻塞,该函数直接返回。
	
		首先通过参见man sem_timedwait函数,查看struct timespec的结构体如下。
		struct timespec {
			time_t tv_sec;		// seconds秒 
			long   tv_nsec;		// nanosecondes纳秒
		}																		
	
		参3abstime:绝对时间,从1970年1月1日开始算。
		如: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); 获取从1970年1月1日开始算的当前时间,即与abstime同单位的绝对时间。
		struct timespec t;	定义timespec结构体变量t。
		t.tv_sec = cur+1; 定时1秒
pthread_cond_timedwait (&cond, &mutex, &t); 传参参APUE.11.6线程同步条件变量小节

		在讲解setitimer函数时我们还提到另外一种时间类型:
        struct timeval {
             time_t      tv_sec;  	// seconds秒
             suseconds_t tv_usec; 	// microseconds微秒
        };
*/

上面说的这么多,参3的传参就是必须加上1970年1月1日的时间就行。例如10s+time(NULL)就是10s后的不再阻塞。

5)pthread_cond_signal函数

int pthread_cond_signal(pthread_cond_t *cond);
/*
	功能:唤醒至少一个阻塞在条件变量的线程。注意:不是唤醒一个。
	参1:条件变量。
*/

6)pthread_cond_broadcast函数

int pthread_cond_broadcast(pthread_cond_t *cond);
/*
	功能:唤醒所有阻塞在条件变量的线程。
	参1:条件变量。
*/

3 总结

  • 1)条件变量的使用一般(可以说必须)依赖锁。
  • 2)必须了解pthread_cond_wait函数的三个功能和顺序。该函数可以让我们知道条件变量和锁都有阻塞功能。条件变量的阻塞是防止CPU占有率过高,锁的阻塞是防止数据访问混乱。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值