作者:小 琛
欢迎转载,请标明出处
为什么会有线程安全问题
我们知道,多线程编程时,创建出来的线程对应同一个虚拟地址空间,即拥有相同的数据,假设多个线程同时运行,都在访问同一个临界资源,就会出现问题。
例如:
因此,为了避免程序出现二义性,要保证在同一时刻,一个临界资源只能被一个执行流访问。
互斥锁的引入
互斥锁:本质上是一个计数器,而这个计数器的取值只有0或者1。
0代表不能加锁,意味着不能去访问临界资源,互斥锁当中的加锁接口就会进行阻塞;
1代表可以枷锁,意味着可以去访问临界资源,互斥锁中的加锁接口会正常返回。
互斥锁的使用函数接口
定义互斥锁
pthread_mutex_t ://互斥锁变量类型
初始化互斥锁
int pthread_mutex_init (pthread_mutex_t* mutex, pthread_mutexattr_t * attr);
mutex: 传入互斥锁变量的地址
attr:设置互斥锁属性,通常我们使用默认属性传入NULL
加锁
- 阻塞加锁:
int pthread_mutex_lock(pthread_mutex_t* lock)- 非阻塞加锁:
int pthread_mutex_trylock(pthread_mutex_t* lock)
规则:如果互斥锁当中计数器的值为1,则表示可以加锁,正常调用;如果互斥锁当中的计数器值为0,表示不可以加锁,报错返回。- 带有超时时间的加锁
int pthread_mutext_timelock(pthread_mutex_t* lock,const struct timespec*)
规则:当加锁时,若当前资源被其它执行流占用,则等待规定时间,若仍无法获取锁资源,报错返回
解锁
int pthread_mutex_unlock(pthread_mutex_t* lock)
执行流,不论采用哪种方式加锁,都可以调用该接口进行解锁。
摧毁互斥锁
int pthread_mutex_destory(pthread_mutex_t* lock)
释放动态初始化的互斥锁变量,防止内存泄漏
例子:抢票系统
假设有100张票,使用多线程编程,多个线程同时对100张票进行消耗,即- -操作,若不使用互斥锁,很可能导致某张票被抢好几次,很明显不符合逻辑。
使用如下代码,加入了互斥锁,一切正常
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREADCOUNT 4
int g_tickes = 100;
pthread_mutex_t lock;
void* ThreadStart(void* arg)
{
(void)arg;
while(1)
{
pthread_mutex_lock(&lock);
if(g_tickes > 0)
{
g_tickes--;
usleep(100000);
printf("i am thread [%p], i have ticket num is [%d]\n", pthread_self(), g_tickes + 1);
}
else
{
pthread_mutex_unlock(&lock);
break;
}
pthread_mutex_unlock(&lock);
}
return NULL;
}
int main()
{
pthread_mutex_init(&lock, NULL);
pthread_t tid[THREADCOUNT];
int i = 0;
for(; i < THREADCOUNT; i++)
{
int ret = pthread_create(&tid[i], NULL, ThreadStart, NULL);
if(ret < 0)
{
perror("pthread_create");
return 0;
}
}
for(i = 0; i < THREADCOUNT; i++)
{
pthread_join(tid[i], NULL);
}
pthread_mutex_destroy(&lock);
return 0;
}
死锁的概念
死锁:程序中有一个执行流没有释放资源,导致其它想获取该互斥锁的执行流进入阻塞等待,而该执行流还想申请对方的锁,形成闭环,导致死锁。
死锁的4个条件
1、互斥:每一把锁都只能被一个执行力占用
2、请求+保持:每一个执行流占用一个锁,但仍在申请新的锁
3、循环等待:若干个执行力在拥有和占用情况下,形成了一个闭环
4、不可剥夺:互斥锁只能被当前拥有者释放
避免死锁
1、破坏必要条件
2、加锁顺序一直
3、避免锁的不被释放
4、一次性分配资源