多线程(8)多线程同步之互斥量+条件变量(linux实现)

1 互斥量定义

  1. 互斥量用来确保一个线程独占一个资源的访问。
  2. 互斥量是一个内核对象。
  3. 互斥量与临界区非常相似,并且互斥量可以用于不同进程中的线程互斥访问。

2 互斥量

2.1 互斥量接口(linux)

2.1.1 pthread_mutex_t mutex

//创建一个互斥对象

2.1.2 pthread_mutex_init

//初始化一个互斥锁;
原型:
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr);

2.1.3 pthread_mutex_lock

//加锁,如果不成功,阻塞等待;
原型:
int pthread_mutex_lock(pthread_mutex_t *mutex);

2.1.4 pthread_mutex_unlock

//解锁;
原型:
int pthread_mutex_unlock(pthread_mutex_t *mutex);

2.1.5 pthread_mutex_trylock

//测试加锁,非阻塞的锁定互斥锁,如果不成功就立即返回,错误码为EBUSY;
原型:
int pthread_mutex_trylock( pthread_mutex_t *mutex );

2.1.6 pthread_mutex_destroy

//注销一个互斥锁;
原型:
int pthread_mutex_destroy(pthread_mutex_t *mutex);

3.1.2 互斥量存在的问题

1. 问题: 互斥锁一个明显的缺点是他只有两种状态:锁定和非锁定,所以经常导致死锁。
2. 解决: 解决死锁的方案就是采用条件变量。
3. 条件变量的作用: 条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,他常和互斥锁一起使用。

3 条件变量: cond

3.2 什么是条件变量?

3.2.1. 条件变量定义

条件变量: cond
是线程同步的一种手段。
其用来阻塞一个线程,直到条件为真为止。一般和互斥量同时使用。

3.2.2 条件变量的一般使用及作用

1. 使用:
一个/多个线程等待"条件变量的条件成立"而挂起;
另一个线程使"条件成立"信号。

2. 具体实现:

  1. 条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。
  2. 一旦其他的某个线程改变了条件变量,他将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。
  3. 这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线承间的同步。

3. 作用:

  1. 条件变量用在某个线程需要在某种条件才去保护它将要操作的临界区的情况下,从而避免了线程不断轮询检查该条件是否成立而降低效率的情况,这是实现了效率提高。

3.2.3 条件变量接口

1. /* 初始化一个条件变量 */
   int pthread_cond_init (pthread_cond_t* cond, pthread_condattr_t *cond_attr);
   
2. /* 销毁一个条件变量 */
   int pthread_cond_destroy(pthread_cond_t* cond);
   
3. /* 令一个消费者等待在条件变量上 */
   int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
   
4. /* 生产者通知等待在条件变量上的消费者 */
   int pthread_cond_signal(pthread_cond_t* cond);

4 互斥量和条件变量使用伪代码

In Thread1:消费者
	pthread_mutex_lock(&mutex); // 拿到互斥锁,进入临界区
	while( 条件为假)
		pthread_cond_wait(cond, mutex); // 令进程等待在条件变量上
	//修改条件
	pthread_mutex_unlock(&mutex); // 释放互斥锁

In Thread2:生产者
	pthread_mutex_lock(&mutex); // 拿到互斥锁,进入临界区
	//设置条件为真
	pthread_cond_signal(cond); // 通知等待在条件变量上的消费者
	pthread_mutex_unlock(&mutex); // 释放互斥锁

5. 通俗易懂说生产者和消费者问题

5.1 通俗易懂说什么是生产者,消费者?

2. 通俗易懂说

妈妈负责做饭,你负责吃,你从饭桌上夹菜吃饭
如果饭桌上有饭,则你去吃,此时妈妈等你吃完再做
如果饭桌没饭了,则妈妈去做饭,此时你需要等妈妈做完再吃

生产者生产物品,消费者消费物品。

  1. 生产者生产数据到缓冲区中,消费者从缓冲区中取数据。
  2. 如果缓冲区已经满了,则生产者线程阻塞;
  3. 如果缓冲区为空,那么消费者线程阻塞。

5.2 什么是生产者,消费者(线程角度理解)?

  1. 所谓生产者-消费者问题,实际上主要是包含了两类线程,一种是生产者线程用于生产数据,另一种是消费者线程用于消费数据;为了解耦生产者和消费者的关系;

  2. 通常会采用共享的数据区域,就像是一个仓库,生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为;而消费者只需要从共享数据区中去获取数据,就不再需要关心生产者的行为。

  3. 但是,这个共享数据区域中应该具备这样的线程间并发协作的功能:

    1. 如果共享数据区已满的话,阻塞生产者继续生产数据放置入内;
    2. 如果共享数据区为空的话,阻塞消费者继续消费数据;

6 代码例子

#include <unistd.h>
#include <pthread.h>

#define CONSU_THREAD_COUNT 2	  //消费者线程数目
#define PRO_THREAD_COUNT 1        //生产者线程数目

//互斥量,条件变量定义
pthread_mutex_t g_mutex ;
pthread_cond_t g_cond ;

pthread_t g_thread[CONSU_THREAD_COUNT + PRO_THREAD_COUNT ] ;

//全局变量,产品
//生产者生产产品(g_iProduct 加1),消费者消费产品(g_iProduct 减1)
int g_iProduct = 0; 

//消费者线程
void* consumer_thread( void* arg )
{
	int iThread_Num = (int)arg ;
	while ( 1 ) 
	{
  		//加锁
 		 pthread_mutex_lock( &g_mutex ) ;

 		//如果没有产品,即g_iProduct 为0,则消费者需要等待产品生产完成,才能消费
  		while ( g_iProduct == 0 ) 
 		{
     		 printf( "consumer %d begin wait a condition...\n", iThread_Num ) ;
    		 // 使线程阻塞在 条件变量这里,等待条件变量就绪
    		 pthread_cond_wait( &g_cond, &g_mutex ) ;
 		 }
  		// 跳出while循环,说明生产者将产品生产完成,g_iProduct != 0
  		printf( "消费者 %d 结束等待条件变量...\n", iThread_Num ) ;
  		printf( "消费者 %d 开始消费产品...\n", iThread_Num ) ;
  		
  		//产品消费完毕,减1
  		--g_iProduct ;
  		printf( "消费者 %d 结束消费产品...\n", iThread_Num ) ;
  		
		//解锁
 		pthread_mutex_unlock( &g_mutex ) ;
 		
		//等待
  		sleep( 1 ) ;
  	}
  	
  	return NULL;
}


//生产者线程
void* producer_thread( void* arg )
{
	int iThread_Num = (int)arg ;
	while ( 1 )
	{
  		//加锁
  		pthread_mutex_lock( &g_mutex ) ;

 	 	// 生产者生产产品,g_iProduct 加1
  		printf( "生产者%d 开始生产产品...\n", iThread_Num ) ;
  		++ g_iProduct;
  		printf( "生产者%d 结束生产产品...\n", iThread_Num ) ;
  		
  		//通知等待在条件变量上的消费者
  		pthread_cond_signal( &g_cond ) ;
  		printf( "生产者%d 通知等待在条件变量上的消费者可以消费产品了..\n", iThread_Num ) ;
  		
  		//解锁
  		pthread_mutex_unlock( &g_mutex ) ;
  		sleep( 5 ) ;
	}

	return NULL ;
}

//测试函数
void test()
{
	//互斥量,条件变量初始化
	 pthread_mutex_init( &g_mutex, NULL ) ;
	 pthread_cond_init( &g_cond, NULL ) 
	 
	 //创建消费者线程
	 for ( int iLoop = 0; iLoop  < CONSUMERS_COUNT; ++ iLoop  )
 	 {
  		pthread_create( &g_thread[iLoop ], NULL, consumer_thread, (void*)iLoop  ) ;
	 }
	 sleep( 1 ) ;
	 
	 //创建生产者线程
	 for ( int iLoop  = 0; iLoop  < PRODUCERS_COUNT; ++ iLoop  )
	 {
	   	pthread_create( &g_thread[iLoop ], NULL, producer_thread, (void*)iLoop  ) ;
	 }
	 
	//等待消费者和生产者线程结束
	for ( int iLoop  = 0; iLoop  < CONSUMERS_COUNT + PRODUCERS_COUNT; ++ iLoop  ) 
	{
  		pthread_join( g_thread[iLoop ], NULL ) ;
	}
	
	//销毁互斥量和条件变量
	pthread_mutex_destroy( &g_mutex ) ;
	pthread_cond_destroy( &g_cond ) ;
	
	rerurn ;
}

5. 参考

  1. https://blog.csdn.net/chengonghao/article/details/51779279
  2. https://www.cnblogs.com/wsw-seu/p/8036186.html
发布了142 篇原创文章 · 获赞 12 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/lqy971966/article/details/104524126