目录
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;
}