多线程编程总结(二)——条件变量和互斥锁
2019-07-14 11:38 发布
生成海报
互斥锁
1.什么叫互斥锁?
互斥锁(也成互斥量)可以用于保护关键代码段,以确保其独占式的访问 ,类似于二元信号量。二者都可以称为挂起等待锁 ———锁资源得不到满足,就会被挂起,在信号量或互斥锁上等待。
注:当前线程的PCB在互斥量的等待队列等待以便快速唤醒。进程等待的本质是将PCB列入某个队列等待。
2.使用方式
当进入关键代码段时,需要申请互斥锁,如果失败就挂起等待,将PCB列入互斥锁的等待队列,成功就将其加锁,等价于二元信号量的P操作;离开关键代码时,对互斥锁进行解锁,以便唤醒其他等待该互斥锁的线程,这等价二元信号量的V操作。
3.互斥锁相关的五个函数
#include
//初始化
int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t* mutexattr);
//销毁
int pthread_mutex_destroy(pthread_mutex_t * mutex);
//加锁阻塞版
int pthread_mutex_lock(pthread_mutex_t * mutex);
//加锁非阻塞版
int pthread_mutex_trylock(pthread_mutex_t * mutex);
//解锁
int pthread_mutex_unlock(pthread_mutex_t * mutex);
参数解析:
MU
1.mutex指向操作的目标锁,互斥锁的类型是pthread_mutex_t的结构体;
2. 初始化时mutexattr参数指定互斥锁的属性。一般设置为NULL取它的默认属性。
3.如果 mutex变量是静态分配的(全局变量或static变量) ,则可以使用宏定义PTHREAD_MUTEX_INITIALIZER初始化一个互斥锁。宏定义实际上是把互斥锁的每个字段都初始化为0。
4.销毁互斥锁是为了释放其占用的内核资源,不能销毁一个已经加锁的互斥锁。
5. pthread_mutex_lock函数以原子操作给一个互斥锁加锁。一个线程可以调用pthread_mutex_lock获得Mutex,如果这时另一个线程已经用pthread_mutex_lock获得了 该Mutex,则当前线程需要挂起等待,直到另一个线程调用 pthread_mutex_unlock释放 Mutex,当前线程被唤醒,才能获得该Mutex并继续执行。
6.如果一个线程既想获得锁,又不想挂起等待,可以调用pthread_mutex_trylock,如果Mutex已经被 另一个线程获得,这个函数会失败返回EBUSY,而不会使线程挂起等待。
7.上面的函数成功时返回0,失败时返回错误码。
4.互斥锁产生的问题——死锁
死锁:集合中的每一个进程都在等待只能由 本集合中的其他进程 才能引发的事件,那么该组进程是死锁的。
线程死锁情形:
死锁产生的四个必要条件(缺一不可):
(1) 非抢占 。不能强行抢占进程中已有的资源。
(2) 互斥 。一次只能有一个进程使用资源。其他进程不能访问已经分配给其他进程的资源。
(3) 占有且等待 。当一个进程在等待分配其他资源时,其继续占有已分配得到的资源
(4) 循环等待 。存在一个封闭的进程链,使得每个资源至少占有此链中下一个进程所需的资源。
解决死锁的方法:
(1)预防死锁。即破坏死锁产生的四个条件。
(2)避免死锁。保证按相同顺序获得锁。(如果一个进程的请求会导致死锁,则不启动该进程;如果一个进程增加资源的请求会导致死锁,则不允许分配)
条件变量
1.什么叫条件变量?
互斥锁用于同步线程对共享数据的访问的话,那么条件变量则用于在线程之间同步共享数据的值。
条件变量提供一种机制:当某个数据达到某个值的时候,唤醒等待该共享数据的资源。这样就能保证顺序访问
一般与互斥锁一起使用:
(1)因为互斥只能保持访问不出错。(但是由于优先级或其他特性可能导致其他线程得不到资源)
(2)同步保证访问不出错的同时,一般还让多个线程或执行流协同(访问临界资源具有顺序性)
2.与条件变量相关的五个函数
#include
int pthread_cond_init(pthread_cond_t* cond, const pthread_condattr_t* cond_attr);
int pthread_cond_destroy(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);
int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
(1)第一个参数与互斥锁类似,cond指向要操作的目标条件变量,条件变量的类型是pthread_cond_t结构体
(2)cond_attr参数指定条件变量的属性如果设置为NULL采用默认属性。和互斥锁类似,也可以采用
PTHREAD_COND_INITIALIZER初始化静态分配的cond变量
(3)一个Condition Variable(条件变量)总是和一个Mutex搭配使用的。一个线程可以调用pthread_cond_wait在这 个条件变量上阻塞等待,这个函数做以下操作
a: 释放Mutex b: 阻塞等待 c: 当被唤醒时,重新获得Mutex并返回
(4)以上函数成功返回0,失败返回错误码
3.用条件变量和互斥锁完成同步与互斥机制
模型:
生产者生产一个结构体串在链表的表头上,消费者 从表头取走结构体。
#include
#include
#include
#include
#include
typedef int DataType;
typedef struct listNode
{
struct listNode* _next;
DataType _data;
}node, *pnode, *plist;
pnode listHead = NULL;
//定义成全局,初始化简单
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t need_product = PTHREAD_COND_INITIALIZER;
pnode CreateNode(DataType data)
{
pnode newnode = (pnode)malloc(sizeof(node));
newnode->_data = data;
newnode->_next = NULL;
return newnode;
}
void Init(plist *list)
{
if (NULL != list)
{
*list = NULL;
}
}
void PushFront(plist *list, DataType d)
{
assert(list != NULL);
pnode newnode = CreateNode(d);
pnode cur = *list;
if (cur == NULL)
{
*list = newnode;
newnode->_next = NULL;
}
else
{
*list = newnode;
newnode->_next = cur;
}
return;
}
void PopFront(plist *list)
{
assert(list != NULL);
pnode cur = *list;
if (cur == NULL)
{
return;
}
else if (cur->_next == NULL)
{
cur = cur->_next;
free(cur);
*list = NULL;
}
else
{
*list = cur->_next;
free(cur);
cur = NULL;
}
return;
}
void Destroy(plist *list)
{
pnode cur = *list;
while (cur != NULL)
{
*list = cur->_next;
cur = *list;
}
return;
}
void ShowList(plist list)
{
pnode cur = list;
while (cur != NULL)
{
printf("%d ", cur->_data);
cur = cur->_next;
}
printf("
");
}
void *product(void * _val)
{
while (1)
{
sleep(1);
//加锁
pthread_mutex_lock(&lock);
int num = rand() % 100;
Init(&listHead);
PushFront(&listHead, num);
printf("call consum:product success and the value is %d
", num);
//解锁
pthread_mutex_unlock(&lock);
//唤醒等待目标变量的线程
pthread_cond_signal(&need_product);
}
}
void *consum(void *_val)
{
while (1)
{
pthread_mutex_lock(&lock);
while (listHead == NULL)
{
//等待目标条件变量,锁使得pthread_cond_wait操作的原子性
pthread_cond_wait(&need_product, &lock);
}
printf("call product:consum success and the value is %d
", listHead->_data);
PopFront(&listHead);
ShowList(listHead);
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main()
{
pthread_t t_product;
pthread_t t_consum;
pthread_create(&t_product, NULL, product, NULL);
pthread_create(&t_consum, NULL, consum, NULL);
//回收线程
pthread_join(t_product, NULL);
pthread_join(t_consum, NULL);
return 0;
}
运行结果:
实验结果分析:
如果不采用互斥锁使得操作的原子性 ,有可能造成数据的二义性;不使用条件变量使得操作按照一定顺序执行 ,可能导致生产者一直生产或者消费者一直消费,可能导致程序效率低下等问题。
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮