Linux-互斥-互斥锁-死锁
Posted 天津 唐秙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux-互斥-互斥锁-死锁相关的知识,希望对你有一定的参考价值。
文章目录
1. 互斥
1.1 互斥锁的原理
互斥锁的底层是一个互斥量,而互斥量的本质是一个计数器,计数器的取值只有两种,一种是1,一种是0。1表示当前临界资源可以被访问,0表示当前临界资源不可以被访问。
获取/释放互斥锁的逻辑:
1.调用加锁接口,加锁接口内部判断计数器的值是否为1,如果为1,则能访问,并且如果加锁成功之后,就会将计数器的值从1变为0,如果为0,则不能访问。
2.调用解锁逻辑,计数器的值从0变成1,表示资源可用。
加锁:
寄存器和内存当中的计数器的值进行交换,判断寄存器的当中的值是否为1,如果为1,则能够加锁,反之不能。
1.2 互斥锁的接口
初始化互斥锁变量
互斥锁的类型:pthread_mutex_t
1.2.1 静态初始化
pthread_mutex_t mutex = PTHREAD_MUTRX_INITIALIZER
这是一个宏定义,本质上包含多个值
root用户下执行下面语句可以打开查看
vim /user/include/pthread.h
vim /usr/include/bits/pthreadtypes.h
1.2.2 动态初始化
pthread_mutex_init函数
int pthread_mutex_init(pthread_mutex_t * mutex, const pthread_mutexattr_t * attr);
mutex:该参数为出参,由调用者传递一个互斥锁变量的地址,由pthread_mutex_init这个函数进行初始化
attr:互斥锁的属性信息,一般设置为NULL,采用默认属性
注意: 动态初始化互斥锁变量的情况需要动态销毁互斥锁(pthread_mutex_destroy),否则就会造成内存泄露。
1.2.3 加锁
1.阻塞加锁接口:pthread_mutex_lock函数
int pthread_mutex_lock(pthread_mutex_t *mutex);
注意:
如果互斥锁变量当中的计数器的值为1,调用该接口,则加锁成功,该接口调用完毕,函数返回。如果互斥锁变量的那个汇总的计数器的值为0,调用该接口,则调用该接口的执行流阻塞。
2.非阻塞加锁接口:pthread_mutex_trylock函数
int pthread_mutex_trylock(pthread_mutex_t *mutex);
注意:
不管有没有加锁成功,都会返回,所以,需要对加锁结果进行判断,判断是否加锁,如果加锁成功,则操作临界资源,反之,则需要循环获取互斥锁,直到拿到互斥锁。
3.带有超时时间的加锁接口:pthread_mutex_timedlock
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abs_timeout);
超时时间内,如果没有获得互斥锁,则返回,超时时间内,如果获取了互斥锁也直接返回。
注意:
带有超时时间的加锁接口还需要引入头文件time.h
1.2.4 解锁接口
pthread_mutex_unlock函数
int pthread_mutex_unlock(pthread_t* mutex)
注意:
不管是阻塞加锁/非阻塞加锁/timedlock加锁成功都可以使用该接口进行解锁。
1.2.5 销毁接口
pthread_mutex_destroy
int pthread_mutex_destroy(pthread_t* mutex)
功能: 释放动态开辟的互斥锁的资源
1.3 互斥锁的使用
1.3.1 什么时候要初始化互斥锁?
在创建工作线程之前,进行初始化互斥锁。
1.3.2 什么时候进行加锁?
在执行流访问临界资源之前进行加锁操作。
注意:
一个执行流加锁成功之后,再去获取互斥锁,该执行流也会阻塞。加锁之后一定要记得解锁,否则就会导致死锁。
1.3.3 什么时候解锁?
在执行流所有有可能退出的地方进行解锁。
1.3.4 什么时候释放互斥锁资源?
在所有使用该互斥锁的线程全部退出之后,就可以释放该互斥锁了。
1.4 代码验证
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREAD_NUM 2
int g_tickets = 100000;
pthread_mutex_t my_lock;
void* MyThreadStart(void* arg)
{
while(1)
{
pthread_mutex_lock(&my_lock);
if(g_tickets > 0)
{
printf("i have %d, i am %p\\n", g_tickets, pthread_self());
g_tickets--;
}
else
{
pthread_mutex_unlock(&my_lock);
pthread_exit(NULL);
}
pthread_mutex_unlock(&my_lock);
}
return NULL;
}
int main()
{
pthread_mutex_init(&my_lock, NULL);
pthread_t tid[THREAD_NUM];
for(int i = 0; i < THREAD_NUM; i++)
{
int ret = pthread_create(&tid[i], NULL, MyThreadStart, NULL);
if(ret < 0)
{
perror("pthread_create");
return 0;
}
}
for(int i = 0; i < THREAD_NUM; i++)
{
pthread_join(tid[i], NULL);
}
pthread_mutex_destroy(&my_lock);
printf("phread_join end...\\n");
return 0;
}
因为我这使用的这个机器只有一个CPU所以,线程只能是并发的执行。
2. 死锁
2.1 死锁的定义
简单的定义:当一个执行流获取到互斥锁之后,并没有进行解锁,就会导致其他执行流由于获取不到锁资源而进行阻塞,我们将这个现象称之为死锁。
复杂的定义:当线程A获取到互斥锁1,线程B获取到互斥锁2的时候,线程A和线程B同时还想获取对方手中的锁(线程A还想获取互斥锁2,线程B还想获取互斥锁1),此时就会导致死锁。
2.2 死锁的现象
2.3 死锁的代码
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
pthread_mutex_t lock_1;
pthread_mutex_t lock_2;
void* MyThread_A(void* arg)
{
pthread_mutex_lock(&lock_1);
sleep(2);
pthread_mutex_lock(&lock_2);
return NULL;
}
void* MyThread_B(void* arg)
{
pthread_mutex_lock(&lock_2);
sleep(2);
pthread_mutex_lock(&lock_1);
return NULL;
}
int main()
{
pthread_mutex_init(&lock_1, NULL);
pthread_mutex_init(&lock_2, NULL);
pthread_t tid_A, tid_B;
pthread_create(&tid_A, NULL, MyThread_A, NULL);
pthread_create(&tid_B, NULL, MyThread_B, NULL);
pthread_join(tid_A, NULL);
pthread_join(tid_A, NULL);
pthread_mutex_destroy(&lock_1);
pthread_mutex_destroy(&lock_2);
return 0;
}
1.执行死锁的代码
2.查看进程号
3.调试正在运行的进程
4.查看当前线程(主线程)的调用堆栈
5.将查看调用堆栈这个功能应用于所有线程
6.跳转到3号线程
7.查看锁的情况
2.4 死锁的必要条件
1.不可剥夺:执行流获取了互斥锁之后,除了自己主动释放锁,其他执行流不能解释该互斥锁
2.循环等待
3.互斥条件:一个互斥锁只能被一个执行流在同一时刻拥有
4.请求与保持:“吃着碗里瞧着锅里”
2.5 预防死锁
1.破坏必要条件:循环等待或者请求与保持
2.加锁顺序一致
3.避免锁没有被释放
4.资源一次性分配
2.6 避免死锁的算法
1.死锁检测算法
2.银行家算法
以上是关于Linux-互斥-互斥锁-死锁的主要内容,如果未能解决你的问题,请参考以下文章