Linux环境下,C语言预防死锁的方法、产生死锁的实际情况(参考APUE的11章以及12章)

一、死锁原因

1、用锁的原因

产生死锁,首先得用到锁,其原因为:

当一个线程修改变量时,其他线程在读取这个变量时可能会看到一个不一致的值。

在变量修改时间多于一个存储器访问周期的处理结构中,当存储器读与存储器写这两个周期交叉时,

这种不一致就会出现

2、产生死锁的原因

  1. 对已拥有的锁加锁。(apue 322页)
void main(void)
{
	 pthread_mutex_lock(&lock)pthread_mutex_lock(&lock)}
  1. 线程1拥有锁A,线程2拥有锁B。此时线程1想要锁B,线程2想要锁A。(apue 323页)
void * th_func1((void *)arg)
{
	pthread_mutex_lock(&lock_A)sleep(10);
	pthread_mutex_lock(&lock_B)}
void * th_func2((void *)arg)
{
	pthread_mutex_lock(&lock_B)sleep(10);
	pthread_mutex_lock(&lock_A)}
  1. 递归加锁问题

    类似原因1
    线程准备调用两个函数:func1()、func2()。而这两个函数都会用到1个以上的线程,且都需操作同一个数据结构。
    因此我们把互斥锁嵌入该数据结构中,调用这两个函数后,都需要对互斥锁加锁。如果func1()调用func2()(互斥锁的属性不是递归类型)就会产生死锁。(apue 348页)

void main(void)
{
	xxx *x;
	func1(x);
	func2(x);
}

void func1((xxx *)x)
{
	pthread_mutex_lock(x->lock);

	func2(x);
	
	pthread_mutex_unlock(x->lock);
}

void func2((xxx *)x)
{
	pthread_mutex_lock(x->lock);
	
	pthread_mutex_unlock(x->lock);
}
  1. 线程获取锁后,未解锁就终止
    线程在未解锁就终止退出的时候,其他线程获取该锁,就会出现死锁的情况
void main(void)
{
	pthread_create(&th1,NULL,th_func,NULL);
	sleep(1);	//等待线程运行
	pthread_mutex_lock(&lock);
}

void * th_func((void *)arg)
{
	pthread_mutex_lock(&lock)return (void*)0;
}

二、解决办法

1、避免对已拥有的锁又加锁(避免原因1)

apue 323页

2、所有的线程均按顺序加锁且一次性获取全部锁(避免原因2)

主要是从编写代码方面去避免。
apue 323页

pthread_t th1,th2;
pthread_mutex_t lock_A,lock_B;
void main(void)
{
	pthread_create(&th1,NULL,func1,NULL);
	pthread_create(&th2,NULL,func2,NULL);
}

void *func1((void*)arg)
{

	pthread_mutex_lock(lock_A);
	pthread_mutex_lock(lock_B);

	sleep(10);
	
	pthread_mutex_unlock(lock_A);
	pthread_mutex_unlock(lock_B);
	return (void *)0;
}

void *func2((void*)arg)
{

	pthread_mutex_lock(lock_A);
	pthread_mutex_lock(lock_B);

	sleep(10);
	
	pthread_mutex_unlock(lock_A);
	pthread_mutex_unlock(lock_B);
	return (void *)0;
}

3、使用绝对时间定时解锁的函数(避免原因1)

互斥锁

apue 327页

int pthread_mutex_timedlock(pthread_mutex_t *restrict __mutex, const struct timespec *restrict __abstime); 
@ __mutex: 
	想要解锁的互斥锁地址 
@ __abstime:   
 	等待的绝对时间 
return:  
 	成功返回0,错误返回错误编号  

读写锁

apue 332页
读锁

int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict __rwlock, const struct timespec *restrict __abstime);
@__rwlock:
	想要加读锁的读写锁地址
@__abstime:
	等待的绝对时间
return:
	成功返回0,错误返回错误编号

写锁

int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict __rwlock, const struct timespec *restrict __abstime);
@__rwlock:
	想要加写锁的读写锁地址
@__abstime:
	等待的绝对时间
return:
	成功返回0,错误返回错误编号

4、尝试解锁(避免原因1)

互斥锁

apue 321页

int pthread_mutex_trylock(pthread_mutex_t *__mutex); 
@__mutex: 
	想要加锁的互斥锁地址
return: 
	成功返回0,错误返回错误编号。
	该锁已被锁住,返回EBUSY 

读写锁

apue 330页

读锁

int pthread_rwlock_tryrdlock(pthread_mutex_t *__rwlock); 
@__rwlock: 
	想要加读锁的读写锁地址
return: 
	成功返回0,错误返回错误编号。
	该锁已被锁住,返回EBUSY 

写锁

int pthread_rwlock_trywrlock(pthread_mutex_t *__rwlock); 
@__rwlock: 
	想要加写锁的读写锁地址
return: 
	成功返回0,错误返回错误编号。
	该锁已被锁住,返回EBUSY 

5、修改互斥锁的类型属性(避免原因1与原因3)

互斥锁有三种属性:进程共享属性、强壮属性、类型属性   

apue 347页
首先先了解互斥锁的类型属性,一共有四种类型:

1、PTHREAD_MUTEX_NORMAL:			不做任何特殊的错误检测或死锁检测 
2、PTHREAD_MUTEX_ERRORCHECK:		提供错误检测 
3、PTHREAD_MUTEX_RECURSIVE:		允许多次递归的加锁,递归互斥锁维护锁计数,
										解锁与加锁次数不匹配则不解锁 (方法7会详细举例说明)
4、PTHREAD_MUTEX_DEFAULT:			linux默认为normal  
互斥量类型没有解锁时重新加锁?不占用时解锁?在已解锁时解锁?
PTHREAD_MUTEX_NORMAL死锁未定义未定义
PTHREAD_MUTEX_ERRORCHECK返回错误 值为35返回错误 值为1返回错误 值为1
PTHREAD_MUTEX_RECURSIVE允许返回错误 值为1返回错误 值为1
PTHREAD_MUTEX_DEFAULT未定义未定义未定义

注:

没有解锁时重新加锁:	也就是原因1,lock->lock
不占用时解锁:			线程1:lock 锁A,线程2:unlock 锁A 
在已解锁时解锁:			lock ->unlock ->unlock

我并未找到对应错误的返回值的宏定义名,知道的大佬可以评论区留言
int pthread_mutexattr_settype(pthread_mutexattr_t *__attr, int __kind);  
@__mutex: 
	互斥锁属性地址 
@__kind: 
	设置 互斥锁的类型属性  
return 
	成功返回0,错误返回错误编

因此我们只需要将 互斥锁的类型属性 设置为错误检测属性PTHREAD_MUTEX_ERRORCHECK,在获取互斥锁后再获取锁就会报错,而不是死锁。

6、创建需要调用的不需要上锁的副本函数(避免原因3)

apue 349页

void main(void)
{
	xxx *x;
	func1(x);
	func2(x);
}

void func1((xxx *)x)
{
	pthread_mutex_lock(x->lock);

	func2_locked(x);			//func2的副本函数,不进行加锁解锁操作,避免递归加锁
	
	pthread_mutex_unlock(x->lock);
}

void func2((xxx *)x)
{
	pthread_mutex_lock(x->lock);
	
	func2_locked(x);
	
	pthread_mutex_unlock(x->lock);
}

void func2_locked(x)
{
	...
}

7、设置递归互斥锁属性(避免原因3)

apue 351页
在定时执行任务的时候需要用到的方法。

例如:
接收到任务信息时,主线程需要对信息变量进行保护加锁,计算定时时间,创建线程定时执行任务函数(执行的任务函数也需要对互斥锁加锁)。

  1. 定时时间大于当前的时间,正常开启一个分离的线程。此时主线程解锁,新线程进行等待。到了时间后,执行需要执行的函数正常加解锁,不会产生死锁。
  2. 定时时间小于当前时间动态分配内存失败无法创建新线程,需要直接调用任务函数,此时主线程未解锁,新线程想加锁,就会产生递归加锁,出现死锁。

按方法5设置互斥锁的递归属性,就能避免递归加锁产生的死锁情况。

int pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);  

注意:
是调用不会死锁,而不是创建线程。创建线程仍然会产生死锁。

8、设置互斥锁强壮属性(避免原因4)

apue 346页

互斥锁的强壮属性取值:
1、PTHREAD_MUTEX_STALLED 	设置stalled 则线程无法自动终止,且其他线程获取锁时会死锁。
2、PTHREAD_MUTEX_ROBUST  	设置robust 其他线程获取该锁时,能通过判断加锁的函数返回值,
								是否是EOWNERDEAD,来判断是否有该情况发生。并不会发生死锁情况
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_DEFAULT);

虽然返回的EOWNERDEAD,但是已经获取到锁了,解锁前需要调用下面的函数,指明互斥锁相关状态在互斥锁解锁前是一致的。

int pthread_mutex_consistent(pthread_mutex_t *__mutex);
@__mutex:
	互斥锁地址
return: 
	成功返回0,错误返回错误编号 

如果未调用该函数就解锁,其他线程再次想加锁,则会返回错误ENOTRECOVERABLE,且该锁无法再次使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ySh_ppp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值