互斥量(mutex)
Posted qwangxiao
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了互斥量(mutex)相关的知识,希望对你有一定的参考价值。
互斥量(mutex)
很多变量需要在线程间共享,这个变量就称为共享变量,可以通过共享数据完成线程之间的交互.
但是,多个线程并发的操作共享变量就会出现问题.
如下模拟实现一个网上购票系统:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int ticket = 100; //表示当前的票有多少张
void* BuyTicket(void* arg)
{
char* s = (char*)arg;
while(1)
{
if(ticket > 0)
{
usleep(1000);
printf("%s buy ticket, %d
",s,ticket);
--ticket;
}
else
{
break;
}
}
return NULL;
}
int main()
{
pthread_t t1,t2,t3,t4;
pthread_create(&t1,NULL,BuyTicket,(void*)"thread 1");
pthread_create(&t2,NULL,BuyTicket,(void*)"thread 2");
pthread_create(&t3,NULL,BuyTicket,(void*)"thread 3");
pthread_create(&t4,NULL,BuyTicket,(void*)"thread 4");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
pthread_join(t4,NULL);
return 0;
}
演示出现错误的结果:
出现以上情况的原因:
- if语句判断为真以后,代码可以并发的切换到其他的线程
- usleep这个是模拟漫长的业务过程,在这个业务过程中,就可能有多个线程进入该代码段
- --ticket本就不是一个原子操作
要解决上面的问题,需要做到以下三点:
- 代码必须要有互斥行为: 当代码进入临界区执行的时候,不允许其他线程进入该临界区
- 如果多个线程的代码同时要求执行临界区的代码,并且临界区没有线程在执行,那么只允许一个线程进入临界区
- 如果线程不在临界区执行,那么该线程不能阻止其他线程进入临界区
所以就此引入我们的互斥量
互斥量接口
初始化互斥量
- 静态分配
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER
- 动态分配
int pthread_mutex_init(pyhread_mutex_t* restrict mutex,const pthread_mutexattr_t* restrict attr);
参数:
mutex: 要初始化的互斥量
attr:填为NULL即可
销毁互斥量
int pthread_mutex_destroy(pthread_mutex_t www.feifanyule.cn *mutex);
注意:
- 使用PTHREAD_ MUTEX_ INITIALIZER初始化的互斥量不需要销毁
- 不要销毁一个已经加锁的互斥量
- 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
互斥量的加锁和解锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:
成功返回0,失败返回错误号
调用 pthread_lock 时可能会出现以下情况:
- 互斥量处于未加锁状态,那么就会对这个互斥量加锁,同时返回成功
- 如果这个互斥量已经加锁,这是 pthread_lock 就会进入阻塞状态,等待互斥量解锁
根据互斥量改进上面的购票系统:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t g_www.chaoyueyule.com lock; //创建一个互斥量
int ticket = 100; //表示当前的票有多少张
void* BuyTicket(void* arg)
{
char* s = (char*)arg;
while(1)
{
pthread_mutex_lock(&g_www.120xh.cn lock); //加锁
if(ticket > 0)
{
usleep(100);
printf("%s buy ticket, %d
",s,ticket);
--ticket;
pthread_mutex_unlock(&g_lock); //解锁
}
else
{
pthread_mutex_unlock(&g_lock); //解锁
break;
}
}
return NULL;
}
int main()
{
pthread_mutex_init(&g_www.dashuj5.com lock,NULL); //初始化互斥量
pthread_t t1,t2,t3,t4;
pthread_create(&t1,NULL,BuyTicket,(void*)"thread 1");
pthread_create(&t2,www.tygj1178.com NULL,BuyTicket,(void*)"thread 2");
pthread_create(&t3,NULL,BuyTicket,(void*)"thread 3");
pthread_create(&t4,www.2018yulpt.com NULL,BuyTicket,(void*)"thread 4");
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,www.thqpt.com/ NULL);
pthread_join(t4,NULL);
pthread_mutex_destroy(&g_lock); //销毁互斥量
return 0;
}
死锁
如果在上面的代码中,加了锁以后没有解锁,会出现什么问题呢?
比内存泄露更可怕的事情!!! 死锁!!!
出现内存泄露, 对于一个256G内存的服务器来说, 往往需要很久才会将内存消耗殆尽. 而且企业中的服务 器往往会定期进行 "例行重启".
出现死锁, 系统中的某些线程会直接停止工作, 导致整个服务器的功能瞬间失效!!
死锁的两个常见场景:
- 一个线程获取到锁之后, 又尝试获取锁, 就会出现死锁.
- 两个线程A和B. 线程A获取了锁1, 线程B获取了锁2. 然后A尝试获取锁2, B尝试获取锁1. 这个时候双方都 无法拿到对方的锁. 并且会在获取锁的函数中阻塞等待. 如果线程数和锁的数目更多了, 就会使死锁问题更容易出现, 问题场景更复杂. 对于这种需要获取多个锁的场景, 规定所有的线程都按照固定的顺序来获取锁, 能够一定程度上避免死锁
总结成一句话就是,拿了锁却没有及时释放,就会产生死锁
线程安全和可重入
可重入函数: 在多个执行流中被同时调用不会存在逻辑上的问题.
线程安全函数: 在多线程中被同时调用不会存在问题.
这两个概念都是在描述函数在多个执行流中调用的情况.
- 线程安全 -> 线程
- 可重入函数 -> 线程&信号处理函数
因此可重入的要求比线程安全的要求要更严格!
- 可重入函数一般情况下都是线程安全的.
- 线程安全函数不一定是可重入的.
代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
pthread_mutex_t g_lock;
int g_count = 0;
//当前是线程安全,不可重入的
void Fun()
{
pthread_mutex_lock(&g_lock);
printf("lock
");
++g_count;
sleep(2);
printf("unlock
");
pthread_mutex_unlock(&g_lock);
}
void* ThreadEntry(void* arg)
{
(void)arg;
while(1)
{
Fun();
}
return NULL;
}
void MyHandler(int sig)
{
(void)sig;
Fun();
}
int main()
{
signal(SIGINT,MyHandler);
pthread_mutex_init(&g_lock,NULL);
pthread_t t1;
pthread_create(&t1,NULL,ThreadEntry,NULL);
pthread_join(t1,NULL);
ThreadEntry(NULL);
pthread_mutex_destroy(&g_lock);
return 0;
}
当前的执行流程为:
- main函数创建线程,并且调用线程入口函数ThreadEntry.
-
线程入口函数ThreadEntry内部死循环的调用Fun()函数
- Fun()函数进行 加锁 -> 操作 -> 解锁
如果在线程入口函数执行到加锁以后,解锁之前我们按下了 ctrl+c 会发生什么情况呢?
当我们在ThreadEntry调用lock以后(unlock以前)按下 ctrl+c,这是会触发信号捕捉函数,在信号捕捉函数里会去调用 Fun() 函数.
但是调用Fun()函数的第一步就是解锁,这时锁已经被ThreadEntry函数获取,那么我们的信号捕捉函数就无法获取锁,只能等待锁被释放再去执行.
但是,之前在信号捕捉时候提到过,在执行完信号捕捉函数以前,主函数会一直阻塞的等待,知道信号捕捉函数退出,在接着执行.
这时候就出现了 ThreadEntry 在等待 MyHandler执行完, MyHandler 函数在等待ThreadEntry函数释放锁.所以就阻塞了.
因此对于这个场景下, Fun()函数是线程安全的, 但是不可重入.
以上是关于互斥量(mutex)的主要内容,如果未能解决你的问题,请参考以下文章