【Linux】线程互斥量与条件变量

一.互斥量(mutex)

1.基本概念

在线程之间,因为线程组内所有线程共享进程的地址空间,所以对于每个线程来说,它的绝大多数资源都是与其他线程共享的,所以在多线程程序中,极有可能因为多个线程同时访问临界资源,而造成数据的二义性问题。所以这里就引入了同步与互斥机制来保护临界资源

  • 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归于单个线程,其他线程语法获得者种变量
  • 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程间的交互
  • 多个线程并发的操作共享变量,会带来一些问题

造成这种原因可能因为以下几点:

  1. if语句半段条件为真后,代码可能并发切换到其他线程
  2. sleep是个漫长的过程,在这期间可能有多个线程会进入到该代码段
  3. --ticket本身就不是个原子操作

要解决这些问题,就必须要有一把锁来保证每个线程访问临界资源是互斥的,即任何时间点,临界区访问资源的时候,有且只有一个线程访问。Linux提供的这把锁叫互斥量

2.相关接口

初始化互斥量:

方法一:静态分配

  • pthread_mutex_t  mutex=PTHREAD_MUTEX_INITIALIZER

方法二:

  • int  pthread_mutex_init(pthread_mutex_t  *restrict  mutex,  const  pthread_mutexattr_t  *restrict  attr)
  • 第一个参数为需要初始化的互斥量的地址,第二个参数为NULL

销毁互斥量

  • int  pthread_mutex_destory(pthread_mutex_t  *mutex)

销毁互斥量需要注意:

  • 注意静态分配的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,需确保后面不会有线程再次尝试加锁

互斥量解锁和加锁:

  • int pthread_mutex_lock(pthread_mutex_t  *mutex)
  • int  pthread_mutex_unlock(pthread_mutex_t  *mutex)
  • 成功返回0,失败返回错误码

调用pthread_lock时,可能会遇到以下情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_lock会陷入阻塞,等待互斥量解锁

二.条件变量

1.基本概念:

条件变量是为了实现多个线程可以同步,即多个线程可以协同高效的工作

2.相关接口

初始化:

  • int  pthread_cond_init(pthread_cond_t *restrict  cond,  const pthread_condattr_t  *restrict  attr)
  • 第一个参数为要初始化相关变量,第二个为NULL

销毁:

  • int  pthread_cond_destroy(pthread_cond_t *cond)
  • 参数为要销毁的条件变量

等待条件满足:

  • int  pthread_cond_wait(pthread_cond_t *restrict  cond,  pthread_mutex  *restrict  mutex)
  • 第一个参数为条件变量。第二个参数为互斥锁
  • 这个函数要干两件事。一是在该条件变量下等待,二是在等待期间释放互斥锁,当被signal或其他方式唤醒时,要重新获得锁,并且从等待处继续执行

唤醒等待:

  • int  pthread_cond_broadcast(pthread_cond_t  *cond);//每次唤醒一批
  • int  pthread_cond_signal(pthread_cond_t  *cond)//每次唤醒一个

3.生产者消费者模型:

小案例:

一个生产者线程和一个消费者线程,生产者线程向全局链表中插入节点,消费者线程向全局链表中拿走节点,这里为了实现生产者插入一个结点,消费者就会拿掉一个结点,首先必须用互斥量保证两个线程之间互斥,但是有互斥还不够,还需要在生产者生产一个结点之后不能在生产,而是通知消费者消费,消费者发现链表为空不能在去消费,通知生产者生产结点

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>

typedef int DataType;

typedef struct Node
{
	DataType _data;
	struct Node* _pNext;
}Node,*pNode;

//创建新节点
pNode BuyNewNode(DataType data)
{
	pNode ptr=NULL;
	ptr=(pNode)malloc(sizeof(Node));
	if(NULL==ptr){
		assert(0);
		return NULL;
	}

	ptr->_data=data;
	ptr->_pNext=NULL;
	return ptr;
}

//头插
void ListPushFront(pNode *pHead,DataType data) 
{
	pNode newNode=NULL;
	assert(pHead);
	newNode=BuyNewNode(data);
	
	newNode->_pNext=*pHead;
	*pHead=newNode;
	
}

//头删
void ListPopFront(pNode *pHead)
{
	assert(pHead);
	if(NULL==*pHead){
		return;
	}
		
	pNode delNode=NULL;
	delNode=*pHead;
	*pHead=(*pHead)->_pNext;
	free(delNode);
}

//判空
int ListEmpty(pNode pHead)
{
	if(pHead==NULL)
		return 1;
	return 0;
}
//打印链表
void PrintList(pNode pHead)
{
	while(pHead){
		printf("%d-->",pHead->_data);
		pHead=pHead->_pNext;
	}
	printf("NULL\n");
}

//获取链接节点个数
int ListSize(pNode pHead)
{
	int size=0;
	while(pHead){
		size++;
		pHead=pHead->_pNext;
	}
	return size;
}

pNode pHead=NULL;
//互斥锁
pthread_mutex_t mutex;
//条件变量
pthread_cond_t cond1;
pthread_cond_t cond2;

//生产者
void *producer(void *arg)
{
	//向链表中插入节点
	while(1){
		//访问临界资源前先上锁

		//如果链表中已经有一个节点,就不再生产,需要在该条件变量下等待消费者
		//并且释放锁
		if(ListSize(pHead)==1){
			pthread_cond_wait(&cond2,&mutex);
		}

		//生产者生产
		ListPushFront(&pHead,5);
		printf("i product:\n");
		PrintList(pHead);
		//唤醒消费者
		//这里一定是先唤醒正在等待的消费者,在释放锁
		pthread_cond_signal(&cond1);
		pthread_mutex_unlock(&mutex);
		//这里用时间来表示优先级
		sleep(1);
	}
}

void *consumer(void *arg)
{
	while(1){
		pthread_mutex_lock(&mutex);
		if(ListEmpty(pHead)){
			pthread_cond_wait(&cond1,&mutex);
		}
		ListPopFront(&pHead);
		printf("i consumer:\n");
		PrintList(pHead);

		pthread_cond_signal(&cond2);
		pthread_mutex_unlock(&mutex);
		sleep(3);
	}
}
int main()
{
	pthread_t tid1,tid2;
	pthread_mutex_init(&mutex,NULL);
	pthread_cond_init(&cond1,NULL);
	pthread_cond_init(&cond2,NULL);

	pthread_create(&tid1,NULL,producer,NULL);
	pthread_create(&tid2,NULL,consumer,NULL);

	pthread_join(tid1,NULL);
	pthread_join(tid2,NULL);

	return 0;
}

注:生产者与消费者模型一般都符合3,2,1规则

  • 3种关系:生产者---生产者(互斥关系)  消费者--消费者(互斥关系)  生产者---消费者(同步关系)
  • 2个角色:生产者,消费者角色
  • 1个交易场所:--上面案例中的交易场所就是链表

四.总结

互斥锁就是为了实现线程间互斥衍生的,互斥锁虽然可以保证数据的一致性,可以保证访问临界资源不出现错误,但是却不能保证两个或多个线程协同以某种高效的方式,所以就需要条件变量,互斥本身是多个线程共同竞争一把锁,锁的申请和释放都有锁的持有者去做,同步是一种让多线程可以协同的机制,实现了你申请,我释放,你释放,我申请。本质是协调线程执行的顺序

猜你喜欢

转载自blog.csdn.net/lw__sunshine/article/details/81125598
今日推荐