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

您所在的位置:网站首页 c语言线程互锁 Linux环境下,C语言预防死锁的方法、产生死锁的实际情况(参考APUE的11章以及12章)

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

2023-11-24 10:00| 来源: 网络整理| 查看: 265

Linux环境下,C语言预防死锁的方法(参考APUE的11章以及12章) 一、死锁原因1、用锁的原因2、产生死锁的原因 二、解决办法1、避免对已拥有的锁又加锁(避免原因1)2、所有的线程均按顺序加锁且一次性获取全部锁(避免原因2)3、使用绝对时间定时解锁的函数(避免原因1)互斥锁读写锁 4、尝试解锁(避免原因1)互斥锁读写锁 5、修改互斥锁的类型属性(避免原因1与原因3)6、创建需要调用的不需要上锁的副本函数(避免原因3)7、设置递归互斥锁属性(避免原因3)8、设置互斥锁强壮属性(避免原因4)

一、死锁原因 1、用锁的原因 产生死锁,首先得用到锁,其原因为: 当一个线程修改变量时,其他线程在读取这个变量时可能会看到一个不一致的值。 在变量修改时间多于一个存储器访问周期的处理结构中,当存储器读与存储器写这两个周期交叉时, 这种不一致就会出现 2、产生死锁的原因 对已拥有的锁加锁。(apue 322页) void main(void) { pthread_mutex_lock(&lock); pthread_mutex_lock(&lock); } 线程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 线程准备调用两个函数: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); } 线程获取锁后,未解锁就终止 线程在未解锁就终止退出的时候,其他线程获取该锁,就会出现死锁的情况 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返回错误 值为1PTHREAD_MUTEX_RECURSIVE允许返回错误 值为1返回错误 值为1PTHREAD_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页 在定时执行任务的时候需要用到的方法。

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

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

按方法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,且该锁无法再次使用。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3