面试重点:死锁 条件变量 生产者与消费 信号量细到恐怖。。。。。

死锁

死锁描述的是程序流程无法继续推进的情况——多个线程对锁资源进行争抢获取,但是因为流程推进顺序不当造成互相等待,最终流程无法推进(程序流程因为某种原因卡死无法继续运行);

1.死锁四个必要条件:

(1). 互斥条件:同一时间只有一个线程/进程可以访问操作;

(2). 不可剥夺条件:一个执行流已获得的资源在未使用完之前,不能强行剥夺;(我的锁只有我能解)
(3). 请求与保持条件:一个执行流因请求资源阻塞时,对已获得的资源保持不放;(加了A锁请求B锁,B请求不到,A不释放)

(4). 环路等待条件:若干条执行流之间形成一种头尾相接的循环等待资源的关系;(线程1加了A锁请求B锁,线程2加了B锁请求A锁)

2.避免死锁:

(防止上面的(3)(4))

预防死锁:

(1)破坏死锁的四个必要条件 (2)加/解锁顺序一致; (3)避免锁未释放的场景(请求不到第二个锁则释放已有的锁“非阻塞加锁”); (4)资源一次性分配 

3.避免死锁算法:

同步的实现:通过条件判断对资源获取的合理性——条件变量;
条件变量:pcb等待队列+能够使线程阻塞以及唤醒线程阻塞的接口;

线程间的同步

条件变量

条件变量实现同步:
条件变量提供了使线程阻塞以及唤醒阻塞线程的接口,但具体什么线程阻塞什么时候唤醒需要程序员通过条件变量控制合理的时序逻辑,只需要在合适的时候调用条件变量提供的阻塞/唤醒接口

条件变量使用注意事项:

  • 条件判断必须使用循环操作,避免唤醒多个线程卡在锁上,解锁后资源访问不合理的情况下进行资源访问;
  • 多种角色应该使用多个条件变量,让不同的角色等待在不同的条件变量队列中,便于角色唤醒,防止唤醒角色错误的情况;

1. 定义条件变量变量
pthread_cond_t cond;
2.初始化:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
cond:要初始化的条件变量; attr:NULL
3. 在一个线程不满足资源访问的情况下,使线程阻塞:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
.
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, struct timespec *timeout);
4.在资源访问条件满足之后,唤醒阻塞的线程:
int pthread_cond_broadcast(pthread_cond_t *cond);——全部唤醒
int pthread_cond_signal(pthread_cond_t *cond);——唤醒至少一个
5. 销毁条件变量:
int pthread_cond_destory(pthread_cond_t *cond);

生产者与消费者模型

使用场景:大量数据产出以及处理的场景;

优点:
1.解耦合:模块分离降低功能耦合度;
2.支持并发:支持多对多,前提是中间缓冲区的操作是必须线程安全的;
3.支持忙闲不均:中间缓冲区在压力大的时候缓冲数据在闲的时候处理;

实现一个线程安全的阻塞队列类,其中包含队列以及对外接口(入队,出队);
一个场景下,两个角色、三种关系(1.生产者——生产者《互斥》2.消费者——消费者《互斥》3.生产者——消费者《同步+互斥》)

//实现 class BlockQueue { private:   std::queue<int> _queue;   pthread _cond_t producer_cond;  //生产者等待的条件变量   pthread_cond_t consumer_cond;  //消费者等待的条件变量   pthread_mutex_t _mutex;  //互斥锁保护_queue的操作 public:   BlockQueue() {}   bool Pop(int *data) {}  //线程安全的出队接口   bool Push(const int data) {}  //线程安全的入队接口   ~BlockQueue() {} }; 

生产者与消费者模型代码实现

  1 #include<iostream>   2 #include<stdio.h>   3 #include<queue>   4 #include<pthread.h>   5 using namespace std;   6 #define MAX_QUEUE 5   7 #define NUM_MEMBER 4   8 class blockQueue{   9 private:  10         int _capacity;  11         std::queue<int> _queue;  12         pthread_mutex_t _mutex;  13         pthread_cond_t producer_cond;  14         pthread_cond_t consumer_cond;  15 public:  16         blockQueue(int cap=MAX_QUEUE)  17         :_capacity(cap)  18         {  19         pthread_mutex_init(&_mutex,NULL);  20         pthread_cond_init(&producer_cond,NULL);  21         pthread_cond_init(&consumer_cond,NULL);  22         }  23   24         ~blockQueue()  25         {  26         pthread_mutex_destroy(&_mutex);  27         pthread_cond_destroy(&producer_cond);  28         pthread_cond_destroy(&consumer_cond);  29         }  30   31         bool Push(int data)  32         {  33         pthread_mutex_lock(&_mutex);  34         while(_capacity==_queue.size()){  35         pthread_cond_wait(&producer_cond,&_mutex);  36         }  37         _queue.push(data);  38         pthread_cond_signal(&consumer_cond);  39         pthread_mutex_unlock(&_mutex);  40         return true;  41   42         }  43   44         bool Pop(int *data)  45         {  46         pthread_mutex_lock(&_mutex);  47         while(_queue.empty()){  48         pthread_cond_wait(&consumer_cond,&_mutex);  49         }  50         *data=_queue.front();  51         _queue.pop();  52         pthread_cond_signal(&producer_cond);  53         pthread_mutex_unlock(&_mutex);  54         return true;  55         }  56 };  57   58 void *producer(void* arg)  59 {  60 blockQueue* q=(blockQueue*)arg;  61 int i=0;  62 while(1){  63 q->Push(i);  64 cout<<pthread_self()<<"push data:"<<i++<<endl;  65   66 }  67 return NULL;  68 }  69 void *consumer(void* arg)  70 {  71 blockQueue* q=(blockQueue*)arg;  72 while(1){  73 int data;  74 q->Pop(&data);  75 cout<<pthread_self()<<"get data"<<data<<endl;  76 }  77 return NULL;  78 }  79 int main()  80 {  81 blockQueue q;  82 int count=NUM_MEMBER;  83 int ret=0;  84 pthread_t pid[NUM_MEMBER];  85 pthread_t cid[NUM_MEMBER];  86 int i=0;  87 while(i<NUM_MEMBER){  88 ret=pthread_create(&pid[i],NULL,producer,&q);{  89 if(ret!=0){  90 printf("创建失败n");  91 return -1;  92 }  93 }  94 ++i;  95 }  96 int j=0;  97 while(j<NUM_MEMBER){  98 ret=pthread_create(&cid[i],NULL,consumer,&q);{  99 if(ret!=0){ 100 cout<<"创建失败"<<endl; 101 //printf("创建失败n"); 102 return -1; 103 } 104 j++; 105 } 106 int count_1; 107 while(count_1<NUM_MEMBER){ 108 pthread_join(pid[i],NULL); 109 pthread_join(cid[i],NULL); 110 ++count_1; 111 } 112 } 113 return 0; 114 }  

信号量

本质:信号量本质是一个计数器,提供pcb等待队列和阻塞接口和唤醒接口;
功能:实现进程间/线程间同步、互斥;
互斥实现:通过计数器表示资源只有一个,保证同一时间只有一个线程/进程访问;
同步实现:通过同一时间对资源进行计数,进而判断对以资源的访问是否合理;
P操作:在资源访问之前-1,判断是否合理,不合理则阻塞,合理则返回;
V操作:在产生资源之后+1,唤醒阻塞的线程/进程;

1.定义信号量:sem_t sem;

2.初始化信号量:int sem_init(sem_t *sem, int pshared, unsigned int value);

3.等待信号量 在临界资源访问/获取之前进行P操作:
int sem_wait(sem_t *sem); ——阻塞接口,当前计数小于0则阻塞

int sem_trywait(sem_t *sem); ——非阻塞接口,当前计数小于0则报错返回

int sem_timedwait(sem_t *sem, const struct timespec *timeout); ——限时阻塞操作,在指定时间内若无法满足条件超时则报错返回

返回值:成功的情况下计数-1,成功返回;错误返回-1,错误原因需要通过errno确定(EINTR - 表示阻塞被信号打断;ETIMEDOUT - 表示tinedwait等待超时;EAGAIN - 表示trywait非阻塞情况下计数为0)

4.发布信号量 资源产生后,计数+1,唤醒阻塞(V操作)
int sem_post(sem_t *sem);

5.销毁信号量 int sem_destory(sem_t *sem);

  • 信号量与条件变量的不同:

信号量是通过自身计数实现判断,不需要搭配互斥锁,条件变量需要程序员自己进行条件判断,使用时需要搭配互斥锁;

  • 信号量与互斥锁的不同:
    信号量可以实现互斥,但是更多的是用于同步的实现

多线程:https://blog.csdn.net/weixin_52270223/article/details/115820547?spm=1001.2014.3001.5502
线程安全:https://blog.csdn.net/weixin_52270223/article/details/115835481?spm=1001.2014.3001.5502

如有错误或者补充,评论下;互相学习,互关一波,抱拳了