欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 金融 > Linux高阶——1110—死锁问题原子访问线程控制与调度线程同步

Linux高阶——1110—死锁问题原子访问线程控制与调度线程同步

2025/2/21 3:28:27 来源:https://blog.csdn.net/weixin_74681777/article/details/143729892  浏览:    关键词:Linux高阶——1110—死锁问题原子访问线程控制与调度线程同步

目录

1、旋转锁

2、死锁问题

死锁问题举例:

1、双线程死锁

 代码

成功截图

2、单线程死锁

死锁问题处理:

死锁问题预防:

有向图

3、原子访问

1、原子访问概念

2、原子访问可用函数

原代码

未加锁代码输出

修改后代码

修改后截图

3、ABA问题

4、线程控制与调度线程同步

例子:两个保安两班倒

使用的函数

代码

成功截图


1、旋转锁

与互斥锁高度相似,一个线程占用资源后,其他线程无法访问

如果是互斥锁会陷入阻塞(放弃cpu)等待,但是自旋锁不阻塞(不放弃cpu),重复申请,直到获取锁为止

一般使用旋转锁时,线程是运行态的

旋转锁的优势:线程使用锁的效率好,一些快速响应的场景比较适合使用旋转锁,但是线程开销比较大,持续占用cpu

2、死锁问题

多线程使用锁时产生死锁,这会导致线程永久阻塞,无法唤醒和继续(卡死)

死锁问题产生的原因:

1、请求与保持        2、互斥访问        3、不可剥夺        4、环形等待

当上述四个必要条件都满足的条件下,死锁才会发生

死锁问题举例:

1、双线程死锁

设置两个变量code1和code2,设置两个线程thread1和thread2

设置一个保护code1的锁lock1,设置一个保护code2的锁lock2

线程thread1的行为为给code1上锁lock1,访问code1,解锁unlock1

死锁问题产生:thread1申请lock1,接着去申请lock2,但是lock2是由thread2已经使用的,thread1会被挂起

此时thread2在已经申请到lock2的情况下,去申请lock1,但此时lock1已经被thread1占用,因此thread2挂起等待

 代码

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<pthread.h>
#include<pthread.h>int code1;
int code2;
pthread_mutex_t lock1=PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t lock2=PTHREAD_MUTEX_INITIALIZER;void * busines_1(void * arg)
{while(1){pthread_mutex_lock(&lock1);sleep(0);printf("thread 0x%x ,output code1  %d\n",(int)pthread_self(),code1);pthread_mutex_lock(&lock2);printf("thread 0x%x ,output code2  %d\n",(int)pthread_self(),code2);pthread_rwlock_unlock(&lock2);pthread_rwlock_unlock(&lock1);}   
}void * busines_2(void * arg)
{while(1){   pthread_mutex_lock(&lock2);sleep(0);printf("thread 0x%x ,output code2  %d\n",(int)pthread_self(),code2);pthread_mutex_lock(&lock1);printf("thread 0x%x ,output code1  %d\n",(int)pthread_self(),code1);pthread_rwlock_unlock(&lock1);pthread_rwlock_unlock(&lock2);}
}int main()
{pthread_t tids[2];int i;for(i=0;i<2;i++){if(i==0)pthread_create(&tids[i],NULL,busines_1,NULL);elsepthread_create(&tids[i],NULL,busines_2,NULL);}while(i--)pthread_join(tids[1],NULL);return 0;
}

成功截图

2、单线程死锁

当某个线程对变量code死锁后,再次对其进行上锁,该进程会被挂起,造成死锁

死锁问题处理:

通过破坏,杀死某个死锁线程来解决死锁问题,但以后需要再次创建,后续依然会产生死锁线程

死锁问题预防:

银行家算法:通过向量进行风险评估的方式,例如:资产集合,5把锁,每次线程申请锁时都要先经过风险评估后,再决定能否获取锁,则能避免死锁问题

有向图

有向图检测,使用图点边,记录线程与资源请求的方向,如果图产生了环,则表示死锁发生

3、原子访问

1、原子访问概念

锁技术虽然可以解决多线程操作资源异常的问题,但是伴随着庞大的开销和时间消耗,尝试无锁编程(原子访问 __sync)避免异常访问

锁的核心为:队列等待        原子问题核心:Compare and Swap

原子访问的最核心问题为(V O N)原则

V=内存当前值        O=旧的预期值        N=新预期值

比较V==O,如果相等,表示没有其他人改变过此值,将新值N赋值给内存当前值V

2、原子访问可用函数

__sync_fetch_and_add(&code,2)——原子加操作

第一个参数:要修改变量的地址        第二个参数:增加的数量        返回值:改变前的值

__sync_add_and_fetch(&code,2)

与__sync_fetch_and_add(&code,2)参数相同,但返回值为修改后的值

__sync_sub_and_fetch()——原子减操作

原子操作一般提供一些关于数值的简单操作,如果操作过于复杂,使用其他的原子函数或无锁编程,比如十个线程访问数据库就不能用原子操作

原代码

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<pthread.h>
#include<pthread.h>#define FLAG 5000
int a;
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;void * busines(void* arg)
{int tmp;for(int i=0;i<FLAG;i++){   tmp=a;printf("thread 0x%x ++a %d\n",(int)pthread_self,++tmp);a=tmp;}   
}int main()
{pthread_t tids[2];int i;for(i=0;i<2;i++){   pthread_create(&tids[i],NULL,busines,NULL);}   while(i--){   pthread_join(tids[i],NULL);}   return 0;
}

未加锁代码输出

修改后代码

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<pthread.h>
#include<pthread.h>#define FLAG 5000
int a;void * busines(void* arg)
{for(int i=0;i<FLAG;i++){   __sync_add_and_fetch(&a,1);printf("thread 0x%x code= %d\n",(int)pthread_self,a);}   
}int main()
{pthread_t tids[2];int i;for(i=0;i<2;i++){   pthread_create(&tids[i],NULL,busines,NULL);}   while(i--){   pthread_join(tids[i],NULL);}   return 0;
}

修改后截图

3、ABA问题

为了解决ABA问题,加入版本号进行判断,虽然最后的值与开始预期值相等,也需要查看版本号

4、线程控制与调度线程同步

条件变量技术:若干个线程可以通过判断条件,决定是否执行,如果可执行则完成任务,否则阻塞等待

条件:全局资源        变量:挂起位置与唤醒位置(cond)

默认情况下,多线程执行没有条理和顺序,每个线程使用各自的资源完成各自的任务,让多线程有一个可以判断交汇的场所(全局变量),线程根据变量决定是否执行,实现线程控制协同的目的

条件变量既然是一种协同技术,必然可以进行线程的挂起唤醒控制,不满足执行条件挂起,满足条件唤醒

例子:两个保安两班倒

多个线程工作条件与挂起条件是互补的,所有需要相互唤醒

条件变量的数量取决于执行条件的数量

使用的函数

pthread_cond_t cd;——挂起变量cd

cd=PTHREAD_COND_INITIALLZER

pthread_cond_init(&cd,NULL)

pthread_cond_destroy(&cd)

pthread_cond_wait(&cd,NULL)——调用函数,挂起线程的同时解锁互斥锁,线程被唤醒时再次执行此函数,进行上锁操作

pthread_cond_signal(&cd)——唤醒一个在挂起cd中的线程

pthread_cond_broadcast(&cd)——唤醒所有线程

代码

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<pthread.h>
#include<pthread.h>int cd1;
pthread_mutex_t lock=PTHREAD_MUTEX_INITIALIZER;int day=0;void* t1(void* arg)
{while(1){   pthread_mutex_lock(&lock);if(day==0)pthread_cond_wait(&cd1,&lock);printf("day ,working...\n");day-=1;pthread_cond_signal(&cd1);pthread_mutex_unlock(&lock);}   
}void* t2(void* arg)
{while(1){   pthread_mutex_lock(&lock);if(day==1)pthread_cond_wait(&cd1,&lock);printf("night ,working...\n");printf("day ,working...\n");day-=1;pthread_cond_signal(&cd1);pthread_mutex_unlock(&lock);}   
}int main()
{pthread_t tids[2];for(int i=0;i<2;i++){if(i==0)pthread_create(&tids[i],NULL,t1,NULL);elsepthread_create(&tids[i],NULL,t2,NULL);}while(1)pthread_join(tids[1],NULL);return 0;
}

成功截图

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词