一.互斥量(mutex)
1.基本概念
在线程之间,因为线程组内所有线程共享进程的地址空间,所以对于每个线程来说,它的绝大多数资源都是与其他线程共享的,所以在多线程程序中,极有可能因为多个线程同时访问临界资源,而造成数据的二义性问题。所以这里就引入了同步与互斥机制来保护临界资源
- 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归于单个线程,其他线程语法获得者种变量
- 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程间的交互
- 多个线程并发的操作共享变量,会带来一些问题
造成这种原因可能因为以下几点:
- if语句半段条件为真后,代码可能并发切换到其他线程
- sleep是个漫长的过程,在这期间可能有多个线程会进入到该代码段
- --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个交易场所:--上面案例中的交易场所就是链表
四.总结
互斥锁就是为了实现线程间互斥衍生的,互斥锁虽然可以保证数据的一致性,可以保证访问临界资源不出现错误,但是却不能保证两个或多个线程协同以某种高效的方式,所以就需要条件变量,互斥本身是多个线程共同竞争一把锁,锁的申请和释放都有锁的持有者去做,同步是一种让多线程可以协同的机制,实现了你申请,我释放,你释放,我申请。本质是协调线程执行的顺序