Posix条件变量

      首先理解下: 资源管理——线程同步的意思
    因为进程内的线程分享相同的资源,所以需要在系统级别上设置控制机制来保证数据的完整性。当一个线程修改了某个变量,而另一个线程试图读取它时,或者两个线程同时修改同一变量,就会影响到数据的完整性,为防止这个问题,操作系统提供了一种相互排斥对象,简写为mutex。在多线程程序中,mutex是通过编程来实现的,可防止多个线程在同一时间访问同一资源。当一个线程需要访问某一资源时,它必须先请求一个mutex,一旦线程得到一个mutex,其他想获取同一mutex的线程被阻塞,并处于低CPU占用的等待状态;一旦这个线程完成了数据访问,它会释放对应的mutex,这就允许其他线程获取以访问相关的数据。
    如果mutex实现得不好,将会导致资源饥饿,也就是常说的死锁。资源饥饿发生在当一个或多个线程竞争同一资源时,如果一个线程请求一个mutex两次,也可能会发生死锁

     互斥器(mutex)是加锁原语,用来排他性地访问共享数据,它不是等待原语。在使用mutex的时候,我们一搬都会期望加锁不要阻塞,总是能立刻拿到加锁。然后尽快访问数据,用完之后尽快解锁,这样才不影响并发性和性能,如果需要等待某个条件成立,我们就应该使用如下的条件变量。

        与mutex类似,Unix中的条件变量,也是一种同步机制。条件变量允许线程会合,可让一个线程在有变化时通知另一个线程,这在Windows中,被称为events。。条件变量(condition variable)是在多线程程序中用来实现"等待--》唤醒"逻辑常用的方法。

条件变量利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:

一个线程等待"条件变量的条件成立"而挂起;

另一个线程使“条件成立”。

     为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。线程在改变条件状态前必须首先锁住互斥量,函数pthread_cond_wait把自己放到等待条件的线程列表上,然后对互斥锁解锁(这两个操作是原子操作)。

条件变量的使用简化如下:
条件变量只有一种正确的使用方式,几乎不可能用错。

对于wait端:

1、必须与mutex一起使用,该布尔表达式的读写需要受此mutex保护。

2、在mutex已上锁的时候才能调用wait()

3、把布尔表达条件和wait()放到while循环中

对于signal或broadcast端

1、修改布尔表达式通常要用mutex来保护

2、在signal之前一般要修改布尔表达

3、注意区分signal和broadcast的区别:

 signal通常用于表示资源可用,broadcast通常用于表明状态变化

#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
#include <semaphore.h>

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

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)


#define CONSUMERS_COUNT 1 
#define PRODUCERS_COUNT 1


unsigned short in = 0;
unsigned short out = 0;
unsigned short produce_id = 0;
unsigned short consume_id = 0;

pthread_mutex_t g_mutex;
pthread_cond_t g_cond;
pthread_t g_thread[CONSUMERS_COUNT+PRODUCERS_COUNT];
int nready=0;
void* consume(void *arg)
{
	int i=0;
	int num = (int)arg;
	while (1)
	{
         pthread_mutex_lock(&g_mutex);
         
           while(nready==0)
          //if(nready==0)
            {
            printf("%d.....begin wait a condtion..\n",num);
            pthread_cond_wait(&g_cond,&g_mutex);
               
             }
             
            printf("%d.....end wait a condtion..\n",num);
            printf("%d.....begin consume produce..\n",num);
            --nready; 
           printf("%d.......end consume produce......\n",num);
           pthread_mutex_unlock(&g_mutex);
            sleep(1); 
	}

	return NULL;
}

void* produce(void *arg)
{
	int num = (int)arg;
	int i;
	while (1)
	{
         pthread_mutex_lock(&g_mutex);
        printf("%d begin produce product....\n",num);
        ++nready;
        printf("%d end produce product.....\n",num);
      
//      pthread_mutex_unlock(&g_mutex);
//             sleep(1);
       if(nready>0)
        pthread_cond_signal(&g_cond);
       
        printf("%d.....signal......\n",num);

         pthread_mutex_unlock(&g_mutex);
         
		 sleep(1);
	}
	return NULL;
}

int main(void)
{
	int i=0;
	pthread_mutex_init(&g_mutex,NULL);
        pthread_cond_init(&g_cond,NULL);

	for (i=0; i<CONSUMERS_COUNT; i++)
		pthread_create(&g_thread[i], NULL, consume, (void*)i);
        sleep(1);

	for (i=0; i<PRODUCERS_COUNT; i++)
		pthread_create(&g_thread[CONSUMERS_COUNT+i], NULL, produce, (void*)i);
	
	for (i=0; i<CONSUMERS_COUNT+PRODUCERS_COUNT; i++)
		pthread_join(g_thread[i], NULL);

	pthread_mutex_destroy(&g_mutex);
        pthread_cond_destroy(&g_cond);
	return 0;
}





(一、)pthread_cond_wait

1、对g_mutex进行解锁

2、等待条件,知道有线程向它发起通知

3、重新对g_mutex进行加锁操作

(二、)pthread_cond_signal     

向第一个等待条件的线程发起通知,如果没有任何一个线程处理等待条件的状态,这个通知将被忽略

(三、)pthread_cond_brocadcase向所有等待线程发起通知


(四、)为什么用等待线程中用while循环?

           这是因为可能会存在虚假唤醒”spurious wakeup”的情况。也就是说,即使没有线程调用pthread_cond_signal, 原先调用

pthread_cond_wait的函数也可能会返回。此时线程被唤醒了,但是条件并不满足,这个时候如果不对条件进行检查而往下执行,就

可能会导致后续的处理出现错误。虚假唤醒在linux的多处理器系统中/在程序接收到信号时可能回发生。在Windows系统和JAVA虚拟

机上也存在。在系统设计时应该可以避免虚假唤醒,但是这会影响条件变量的执行效率,而既然通过while循环就能避免虚假唤醒造

成的错误,因此程序的逻辑就变成了while循环的情况。注意:即使是虚假唤醒的情况,线程也是在成功锁住mutex后才能从

pthread_cond_wait()中返回。即使存在多个线程被虚假唤醒,但是也只能是一个线程一个线程的顺序执行,也即:

pthread_mutex_lock   检查/处理 pthread_condition_wai()或者pthread_mutex_unlock(mutex)来解锁.

(五、)解锁和等待转移(wait morphing) 解锁互斥量mutex和发出唤醒信号pthread_cond_signal是两个单独的操作,那么就存在一个顺序的问题。谁先随后可能会产生不同的结果。

如下:

(1) 按照 unlock(mutex); pthread_cond_signal()顺序, 当等待的线程被唤醒时,因为mutex已经解锁,因此被唤醒的线程很容易就锁住了mutex然后从pthread_cond_wait()中返回了。

(2) 按照 pthread_cond_signal(); unlock(mutext)顺序,当等待线程被唤醒时,它试图锁住mutex,但是如果此时mutex还未解锁,则线程又进入睡眠,mutex成功解锁后,此线程在再次被唤醒并锁住mutex,从而从pthread_cond_wait()中返回。

    可以看到,按照(2)的顺序,对等待线程可能会发生2次的上下文切换,严重影响性能。因此在后来的实现中,对(2)的情况,如果线程被唤醒但是不能锁住mutex,则线程被转移(morphing)到互斥量mutex的等待队列中,避免了上下文的切换造成的开销。 

--wait morphing 编程时,推荐采用(1)的顺序解锁和发唤醒信号。而Java编程只能按照(2)的顺序,否则发生异常

Linux系统中说明了这两种顺序不管采用哪种,其实现效果都是一样的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值