【Linux学习笔记】一篇文章彻底搞定 “Linux同步与互斥“ !

本章重点

  • 1. 学会线程同步。

  • 2 学会使用互斥量,条件变量,posix信号量,以及读写锁。

1、进程线程间的互斥相关背景概念

  • 临界资源:多线程执行流共享的资源就叫做临界资源
  • 临界区:每个线程内部,访问临界资源的代码,就叫做临界区
  • 互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用
  • 原子性(后面讨论如何实现):不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成

2、互斥量mutex

  • 大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。
  • 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。
  • 多个线程并发的操作共享变量,会带来一些问题。

前一章我们提到了如果我们定义一个全局变量,所以的线程都可以访问并修改这个全局变量,所以我们把这个全局变量称为共享资源,未来这个共享资源被多线程并发访问的时候,此时我们的一个线程正在访问这个全局变量,而另外一个线程在修改这个全局变量,此时我们就会出现数据不一致的问题,不废话,我们来模拟一下。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <thread>
#include <vector>
#include <stdio.h>
#include <cstring>using namespace std;
#define NUM 4int tickets = 1000; // 用多线程,模拟一轮抢票class threadData
{
public:threadData(int number): _threadname("thread-" + to_string(number)){}public:string _threadname;
};void* getTicket(void* args)
{threadData* td = static_cast<threadData*>(args);const char* name = td->_threadname.c_str(); while(true){if(tickets > 0) //可以抢票{usleep(1000); printf("who = %s, get a ticket: %d\n", name, tickets);tickets--; //每抢到一张票,总量减一}elsebreak;}printf("%s ... quit\n", name);return nullptr;
}int main()
{vector<pthread_t> tids;vector<threadData*> thread_datas;for (int i = 1; i <= NUM; i++){pthread_t tid;threadData* td = new threadData(i);//给线程起名字thread_datas.push_back(td);pthread_create(&tid, nullptr, getTicket, thread_datas[i - 1]);tids.push_back(tid);}for(auto thread: tids)// 等待所有线程{pthread_join(thread, nullptr);}for(auto td : thread_datas){delete td;}return 0;
}

然后我们来运行一下:

我们发现此时票都为负数了,我们居然还能抢到票,哇,要是这样我们买电影票肯定不会买不到。哈哈哈,回顾本题,我们的代码是大于0才会抢票,为什么这里负数,还是可以抢票呢?我们上面提到了tickets这是一个可以被并发访问的共享资源,可以被所有的线程访问和修改,此时数据在无保护的情况下,被多线程并发访问,造成了数据不一致的问题!此时肯定和多线程并发访问是有关系的!我先来看一下数据计算的一般过程。

取出ticket--部分的汇编代码

objdump -d a.out > test.objdump
152 40064b: 8b 05 e3 04 20 00 mov 0x2004e3(%rip),%eax # 600b34
153 400651: 83 e8 01 sub $0x1,%eax
154 400654: 89 05 da 04 20 00 mov %eax,0x2004da(%rip) # 600b34

-- 操作并不是原子操作,而是对应三条汇编指令:

  • load :将共享变量ticket从内存加载到寄存器中
  • update : 更新寄存器里面的值,执行-1操作
  • store :将新值,从寄存器写回共享变量ticket的内存地址

为什么可能无法获得争取结果?

  • if 语句判断条件为真以后,代码可以并发的切换到其他线程
  • usleep 这个模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段
  • --ticket 操作本身就不是一个原子操作

那么这个问题怎么解决呢?

对共享数据的任何访问,保证任何时候只有一个执行流访问!--- 互斥!!!

  • 代码必须要有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且临界区没有线程在执行,那么只能允许一个线程进入该临 界区。
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到这三点,本质上就是需要一把锁。Linux上提供的这把锁叫互斥量。

3、互斥量的接口

初始化互斥量

初始化互斥量有两种方法:

方法1,静态分配:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER

⭐方法2,动态分配:

int pthread_mutex_init(pthread_mutex_t *restrict mutex, 
const pthread_mutexattr_t *restrictattr);
参数:
mutex:要初始化的互斥量
attr:锁的相关属性,可以设置为NULL

销毁互斥量

销毁互斥量需要注意:

  • 使用 PTHREAD_ MUTEX_ INITIALIZER 初始化的互斥量不需要销毁
  • 不要销毁一个已经加锁的互斥量
  • 已经销毁的互斥量,要确保后面不会有线程再尝试加锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥量加锁和解锁

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
返回值:成功返回0,失败返回错误号

调用 pthread_ lock 时,可能会遇到以下情况:

  • 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功
  • 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量, 那么pthread_ lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

接下来我们来使用一下锁,看看下面的代码有什么问题?

此时票只能被一个线程所抢到,线程因为一直执行到票完为止。

此时若if条件不满足,此时执行break,锁资源就一直得不到释放,导致其他线程一直申请不到锁资源。

这样我们就能解决,随后验证一下抢票,改进上面的抢票系统

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <thread>
#include <vector>
#include <stdio.h>
#include <cstring>using namespace std;
#define NUM 4int tickets = 1000; // 用多线程,模拟一轮抢票class threadData
{
public:threadData(int number, pthread_mutex_t* lock): _threadname("thread-" + to_string(number)), _lock(lock){}public:string _threadname;pthread_mutex_t* _lock;
};void* getTicket(void* args)
{threadData* td = static_cast<threadData*>(args);const char* name = td->_threadname.c_str(); while(true){// tickets - 共享资源 - 临界资源// 访问临界资源的代码 - 临界区pthread_mutex_lock(td->_lock);//加锁// 申请锁成功,才能往后执行,不成功,阻塞等待if(tickets > 0) //可以抢票{usleep(1000); printf("who = %s, get a ticket: %d\n", name, tickets);tickets--; //每抢到一张票,总量减一pthread_mutex_unlock(td->_lock);//解锁}else{pthread_mutex_unlock(td->_lock);//解锁break;}}  printf("%s ... quit\n", name);return nullptr;
}int main()
{pthread_mutex_t lock; // 锁 - main函数栈上开辟的空间pthread_mutex_init(&lock, nullptr);// 初始化锁vector<pthread_t> tids;vector<threadData*> thread_datas;for (int i = 1; i <= NUM; i++){pthread_t tid;// 让每个线程都拿到这把锁threadData* td = new threadData(i, &lock);//给线程起名字thread_datas.push_back(td);pthread_create(&tid, nullptr, getTicket, thread_datas[i - 1]);tids.push_back(tid);}for(auto thread: tids)// 等待所有线程{pthread_join(thread, nullptr);}for(auto td : thread_datas){delete td;}// 释放锁pthread_mutex_destroy(&lock);return 0;
}

看看运行结果:

此时我们确实解决了票数为负数的问题,可是为什么票都被thread-1抢去了,难道它是强盗吗?我们先将票数再改大一点,改到十万张,然后将输出结果写到文件里。

./mythread > log.txt   #重定向到log.txt文件中

此时文件比较大,我直接说结果了,此时依然只有一个线程抢到所有票,其他线程一张票都没有抢到,我们来修改一下代码的逻辑。

此时看到就运行成功了,上面的结果一直是一个线程抢票,是因为它离锁资源最近,抢锁速度非常快,导致其他线程抢不到,加上usleep就是让其他线程也能抢到锁,我们抢到票,我们并不会立马抢下一张票,其实在多线程还要执行抢票之后的后续动作,我们这里就是用usleep来模拟抢票之后的后续动作,然后我们再基于下面的图片理解一下上面的情况,同时再理解一些概念。

随后我们来使用第二种方式,定义一个全局的锁。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <thread>
#include <vector>
#include <stdio.h>
#include <cstring>using namespace std;#define NUM 4pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int tickets = 1000; // 用多线程,模拟一轮抢票class threadData
{
public:threadData(int number): _threadname("thread-" + to_string(number)){}
public:string _threadname;
};void* getTicket(void* args)
{threadData* td = static_cast<threadData*>(args);const char* name = td->_threadname.c_str(); while(true){// tickets - 共享资源 - 临界资源// 访问临界资源的代码 - 临界区pthread_mutex_lock(&lock);//加锁// 申请锁成功,才能往后执行,不成功,阻塞等待if(tickets > 0) //可以抢票{usleep(1000); printf("who = %s, get a ticket: %d\n", name, tickets);tickets--; //每抢到一张票,总量减一pthread_mutex_unlock(&lock);//解锁}else{pthread_mutex_unlock(&lock);//解锁break;}usleep(10);  //新加}  printf("%s ... quit\n", name);return nullptr;
}int main()
{pthread_mutex_t lock; // 锁 - main函数栈上开辟的空间pthread_mutex_init(&lock, nullptr);// 初始化锁vector<pthread_t> tids;vector<threadData*> thread_datas;for (int i = 1; i <= NUM; i++){pthread_t tid;// 让每个线程都拿到这把锁threadData* td = new threadData(i);//给线程起名字thread_datas.push_back(td);pthread_create(&tid, nullptr, getTicket, thread_datas[i - 1]);tids.push_back(tid);}for(auto thread: tids)// 等待所有线程{pthread_join(thread, nullptr);}for(auto td : thread_datas){delete td;}// 释放锁pthread_mutex_destroy(&lock);return 0;
}

4、互斥量实现原理探究

申请锁和释放锁本身就被设计成为了原子性操作,如何做到的?

我们再来深入探讨一下,首先我们需要有一个共识

  • 经过上面的例子,大家已经意识到单纯的 i++ 或者 ++i 都不是原子的,有可能会有数据一致性问题
  • 为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用是把寄存器和内存单元的数据相交换,由于只有一条指令,保证了原子性,即使是多处理器平台,访问内存的总线周期也有先后,一 个处理器上的交换指令执行时另一个处理器的交换指令只能等待总线周期。 现在我们把lock和unlock的伪代码看一下

现在我们再来对我们上面的抢票进行封装,更加深入理解锁

makefile

mythread:mythread.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f mythread

LockGuard.hpp

#pragma once#include <pthread.h>class Mutex
{
public:Mutex(pthread_mutex_t* lock): _lock(lock){}void Lock(){pthread_mutex_lock(_lock);}void Unlock(){pthread_mutex_unlock(_lock);}~Mutex(){}
private:pthread_mutex_t* _lock;
};class LockGuard
{
public:LockGuard(pthread_mutex_t* lock): _mutex(lock) //此时对象以及存在{_mutex.Lock();}~LockGuard() {_mutex.Unlock();}
private:Mutex _mutex;
};

mythread.cc

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <cstdlib>
#include <string>
#include <thread>
#include <vector>
#include <stdio.h>
#include <cstring>
#include "LockGuard.hpp"using namespace std;
#define NUM 4int tickets = 1000; // 用多线程,模拟一轮抢票
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;//全局锁class threadData
{
public:threadData(int number): _threadname("thread-" + to_string(number)){}
public:string _threadname;
};void* getTicket(void* args)
{threadData* td = static_cast<threadData*>(args);const char* name = td->_threadname.c_str(); while(true){{// tickets - 共享资源 - 临界资源// 访问临界资源的代码 - 临界区// 申请锁成功,才能往后执行,不成功,阻塞等待LockGuard lockguard(&lock); // 临时的LockGuard对象,RAII风格的锁if(tickets > 0) //可以抢票{usleep(1000); printf("who = %s, get a ticket: %d\n", name, tickets);tickets--; //每抢到一张票,总量减一}else{break;}// 临时的LockGuard对象除出了作用域就会调用析构函数去释放锁}usleep(10);  //让这个不要成为临界区}  printf("%s ... quit\n", name);return nullptr;
}int main()
{pthread_mutex_t lock; // 锁 - main函数栈上开辟的空间pthread_mutex_init(&lock, nullptr);// 初始化锁vector<pthread_t> tids;vector<threadData*> thread_datas;for (int i = 1; i <= NUM; i++){pthread_t tid;// 让每个线程都拿到这把锁threadData* td = new threadData(i);//给线程起名字thread_datas.push_back(td);pthread_create(&tid, nullptr, getTicket, thread_datas[i - 1]);tids.push_back(tid);}for(auto thread: tids)// 等待所有线程{pthread_join(thread, nullptr);}for(auto td : thread_datas){delete td;}// 释放锁pthread_mutex_destroy(&lock);return 0;
}

5、可重入VS线程安全

概念:

  • 线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作, 并且没有锁保护的情况下,会出现该问题。
  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

常见的线程不安全的情况

  • 不保护共享变量的函数
  • 函数状态随着被调用,状态发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数

常见的线程安全的情况

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
  • 类或者接口对于线程来说都是原子操作
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性

常见不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
  • 可重入函数体内使用了静态的数据结构

常见可重入的情况

  • 不使用全局变量或静态变量
  • 不使用用malloc或者new开辟出的空间
  • 不调用不可重入函数
  • 不返回静态或全局数据,所有数据都有函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

可重入与线程安全联系

  • 函数是可重入的,那就是线程安全的
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

可重入与线程安全区别

  • 可重入函数是线程安全函数的一种
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的。

总结一下:

线程安全描述的是线程并发的问题,可重入描述的是函数的特点。

⭐不可重入函数,在多线程访问可能会出现线程安全的问题,但是一个函数可重入,那么不会出现线程问题。

6. 常见锁概念

⭐死锁:死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所占用不会释放的资源而处于的一种永久等待状态,直接代码案例演示现象。

此时线程拿着锁锁资源然后再去申请锁资源,但是没有线程释放锁资源,从而导致所有线程申请锁资源都不成功,所以整个线程全部都卡住了,这就是一个典型的死锁场景。

死锁四个必要条件

⭐必要条件:如果产生了死锁的问题,那么下面的四个条件必须都要满足。

  • 互斥条件:一个资源每次只能被一个执行流使用(前提)
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放(原则)
  • 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺(原则)
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系(重要条件)

避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

避免死锁算法

⭐死锁检测算法

⭐银行家算法

7、Linux线程同步

同步:同步问题是保证数据安全的情况下,让我们的线程访问资源具有一定的顺序性。

条件变量

条件变量必须依赖于锁的使用

  • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。
  • 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

同步概念与竞态条件

  • 同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问 题,叫做同步
  • 竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。在线程场景下,这种问题也不难理解

条件变量函数

初始化

int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrictattr);
参数:
cond:要初始化的条件变量
attr:NULL

销毁

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex);
参数:
cond:要在这个条件变量上等待
mutex:互斥量,后面详细解释

唤醒等待

int pthread_cond_broadcast(pthread_cond_t *cond);//唤醒所有线程
int pthread_cond_signal(pthread_cond_t *cond);//唤醒最近的一个线程
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;int cnt = 0;void* Count(void* args)
{pthread_detach(pthread_self()); //分离进程uint64_t number = (uint64_t)args;while(true){cout << "pthread: " << number << endl;sleep(3);}
}int main()
{for(uint64_t i = 0; i < 5; i++){   pthread_t tid;pthread_create(&tid, nullptr, Count, (void*)i);// 注意,这里不能传入&i,因为此时传过去拿到的是i的地址,// 如果从线程解引用到&i,就可以修改i的值,从而影响主线程的逻辑}while(true) sleep(1);return 0;
}

此时我们来看一下结果:

我们也可以看一下如果传入的是i的地址结果是怎么样的。

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;int cnt = 0;void* Count(void* args)
{pthread_detach(pthread_self()); //分离进程uint64_t number = *(uint64_t*)args;while(true){cout << "pthread: " << number << endl;sleep(3);}
}int main()
{for(uint64_t i = 0; i < 5; i++){   pthread_t tid;pthread_create(&tid, nullptr, Count, (void*)&i);// 注意,这里不能传入&i,因为此时传过去拿到的是i的地址,// 如果从线程解引用到&i,就可以修改i的值,从而影响主线程的逻辑}while(true) sleep(1);return 0;
}

看看运行结果:

此时就会出现意外的情况,所有不能传入i的地址。

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int cnt = 0;void* Count(void* args)
{pthread_detach(pthread_self()); //分离进程uint64_t number = (uint64_t)args;while(true){pthread_mutex_lock(&lock);// 这里要注意除了我们的cnt是临界资源,我们的显示器也是临界资源哟!// 同一个时间内,存在多个线程想向显示器写入内容cout << "pthread: " << number << ", cnt: " << cnt++ << endl;pthread_mutex_unlock(&lock);}
}int main()
{for(uint64_t i = 0; i < 5; i++){   pthread_t tid;pthread_create(&tid, nullptr, Count, (void*)i);// 注意,这里不能传入&i,因为此时传过去拿到的是i的地址,// 如果从线程解引用到&i,就可以修改i的值,从而影响主线程的逻辑}while(true) sleep(1);return 0;
}

此时就和我们的抢票一样,线程5一个人占用锁资源,其他线程根本抢不到,所以此时我们就要使用条件变量,不要让线程一直占有锁。

#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int cnt = 0;void* Count(void* args)
{pthread_detach(pthread_self()); //分离进程uint64_t number = (uint64_t)args;cout << "pthread: " << number << " creat success" << endl;while(true){pthread_mutex_lock(&lock);// 这里要注意除了我们的cnt是临界资源,我们的显示器也是临界资源哟!// 同一个时间内,存在多个线程想向显示器写入内容// 不管临界资源的状态情况//?为什么在这里 1.pthread_cond_wait让线程等待的时候,会自动释放锁!// 所有线程一申请锁,立马释放锁并去排队pthread_cond_wait(&cond, &lock); cout << "pthread: " << number << ", cnt: " << cnt++ << endl;pthread_mutex_unlock(&lock);}
}int main()
{for(uint64_t i = 0; i < 5; i++){   pthread_t tid;pthread_create(&tid, nullptr, Count, (void*)i);// 注意,这里不能传入&i,因为此时传过去拿到的是i的地址,// 如果从线程解引用到&i,就可以修改i的值,从而影响主线程的逻辑}sleep(3);// 保证所有线程都已经在队列排队cout << "main thread ctrl begin: " << endl;// 主线程来唤醒while(true){sleep(1);// 唤醒指定条件变量下的等待队列的一个线程,默认都是第一个pthread_cond_signal(&cond);cout << "signal one thread..." << endl;}return 0;
}

运行结果:

此时我们就能看到所有的线程能按照一定的次序来得到锁资源,我们再来使用一下唤醒一批线程的接口。

细节问题:为什么线程等待在线程加锁之后???

这里我们就要详细解释一下,就拿我们的抢票来说,我们抢到了锁不一定就能成功抢到票,你肯定此时要有票吧,没有票资源你抢到了锁也只能干等着,所以线程等待必须在线程加锁之后,因为判断临界资源是否满足,也是在访问临界资源!判断资源是否就绪(也就是还有没有票),是通过在临界资源内部实现的,此时当资源没有就绪(也就是没有票了),那么你肯定要等待工作人员发票,你肯定就要等待,可是你不要忘了,此时你是带锁等待的哟!未来票资源准备好了有锁你就能买票,但是此时你直接带锁去等待前面的线程锁的释放,但是别的线程都没有申请到锁你怎么让它释放锁呢?,导致其他线程无法申请锁,其他人也都在等,所以此时程序阻塞了,所以设计条件变量的人也想到了这个问题,它就将pthread_cond_wait的时候,会自动释放锁,当未来唤醒返回的时候,我们一定是重新申请了锁。

细节问题:为什么 pthread_cond_wait 需要互斥量?

  • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须 要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件 变量上的线程。
  • 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有 互斥锁就无法安全的获取和修改共享数据。

按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不就行了,如下代码:

// 错误的设计 
pthread_mutex_lock(&mutex);
while (condition_is_false) {pthread_mutex_unlock(&mutex);//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过 pthread_cond_wait(&cond);pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);
  • 由于解锁和等待不是原子操作。调用解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到 互斥量,摒弃条件满足,发送了信号,那么 pthread_cond_wait 将错过这个信号,可能会导致线程永远 阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是一个原子操作。
  • int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 进入该函数后, 会去看条件量等于0不?等于,就把互斥量变成1,直到cond_ wait返回,把条件量改成1,把互斥量恢复 成原样。

条件变量使用规范

等待条件代码

pthread_mutex_lock(&mutex);
while (条件为假)pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);

⭐给条件发送信号代码

pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/326120.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Springboot项目使用redis实现session共享

1.安装redis&#xff0c;并配置密码 这里就不针对于redis的安装约配置进行说明了&#xff0c;直接在项目中使用。 redis在windows环境下安装&#xff1a;Window下Redis的安装和部署详细图文教程&#xff08;Redis的安装和可视化工具的使用&#xff09;_redis安装-CSDN博客 2…

C++青少年简明教程:C++程序结构

C青少年简明教程&#xff1a;C程序结构 一个简单的C程序源码如下&#xff1a; #include <iostream> using namespace std;int main() {cout << "Hello World" << endl;return 0; }下面解析一下。 1. #include <iostream> 这是一条预处理…

Request请求数据 (** kwargs参数)

目录 &#x1f31f;前言&#x1f349;request入门1. params2. data3. json4. headers5. cookies6. auth7. files8. timeout9. proxies10. allow_redirects11. stream12. verify13. cert &#x1f31f;总结 &#x1f31f;前言 在Python中&#xff0c;发送网络请求是一项常见的任…

xCode升级后: Library ‘iconv2.4.0’ not found

报错信息&#xff1a; targets 选中 xxxNotification: Build Phases ——> Link Binary With Libraries 中&#xff0c;移除 libiconv.2.4.0.tbd libiconv.2.4.0.dylib 这两个库&#xff08;只有一个的移除一个就好&#xff09;。 然后重新添加 libiconv.tbd 修改完…

日本率先研发成功6G设备,刺痛了谁?为何日本能率先突破?

日本率先研发成功6G设备&#xff0c;无线数据速率是5G的百倍&#xff0c;这让日本方面兴奋莫名&#xff0c;毕竟日本在科技方面从1990年代以来太缺少突破的创新了&#xff0c;那么日本为何如今在6G技术上能率先突破呢&#xff1f; 日本在1980年代末期达到顶峰&#xff0c;它的科…

基于springboot+mybatis+vue的项目实战之(后端+前后端联调)

步骤&#xff1a; 1、项目准备&#xff1a;创建数据库&#xff08;之前已经创建则忽略&#xff09;&#xff0c;以及数据库连接 2、建立项目结构文件夹 3、编写pojo文件 4、编写mapper文件&#xff0c;并测试sql语句是否正确 5、编写service文件 6、编写controller文件 …

机器学习1——线性回归、误差推导

有监督——分类、回归 一、线性回归 对于一个线性方程&#xff0c;没办法拟合所有的数据点&#xff0c;但是要尽可能的覆盖尽可能多的点。 在下面的图中&#xff0c;x01。添加这一项的目的是&#xff1a;将数据矩阵补全&#xff08;比如年龄是x1、工资是x2&#xff0c;那么x0手…

JS解密之新js加密实战(二)

前言 上次发了一篇关于新加密的&#xff0c;只解了前边两层&#xff0c;这中间家里各种事情因素影响&#xff0c;没有继续进一步研究&#xff0c;今天百忙之中抽空发布第二篇&#xff0c;关于其中的一小段加密片段&#xff0c;我认为分割成多个小片段是更容易被理解的。逻辑相…

大规模 RGB LED灯控系统 Lumos:创新与智能化的融合

灯控系统&#xff1a;创新与智能化的融合 在现代照明技术不断进步的背景下&#xff0c;灯控系统的应用已经从简单的开关控制&#xff0c;发展到能够进行复杂程控操作的智能化管理。我们推出的新一代灯控解决方案&#xff0c;凭借其高度的可配置性和跨平台兼容性&#xff0c;已…

LVDS 源同步接口

传统数据传输通常采用系统同步传输方式&#xff0c;多个器件基于同一时钟源进行系统同步&#xff0c;器件之间的数据传输时序关系以系统时钟为参考&#xff0c;如图1所示。系统同步传输方式使各器件处于同步工作模式&#xff0c;但器件之间传输数据的传输时延难以确定&#xff…

大语言模型的数据预处理

文章目录 质量过滤敏感内容过滤数据去重 当收集了丰富的文本数据之后&#xff0c;为了确保数据的质量和效用&#xff0c;还需要对数据进行预处理&#xff0c;从而消除低质量、冗余、无关甚可能有害的数据。一般来说&#xff0c;需要构建并使用系统化的数据处理框架&#xff08;…

Find My腰包|苹果Find My技术与腰包结合,智能防丢,全球定位

腰包具有显瘦和显高的双重功效&#xff0c;它不仅能提高腰线、拉长腿部线条&#xff0c;还能遮住腹部多余的赘肉&#xff0c;从而在视觉上达到变高的效果&#xff0c;使整体看起来更加显瘦。除了时尚功能&#xff0c;腰包在运动中也有其独特的用途。例如&#xff0c;在跑步时&a…

大数据项目中的拉链表(hadoop,hive)

缓慢渐变维 拉链表 拉链表&#xff0c;可实现数据快照&#xff0c;可以将历史和最新数据保存在一起 如何实现: 在原始数据增加两个新字段 起始时间&#xff08;有效时间&#xff1a;什么时候导入的数据的时间&#xff09;&#xff0c;结束时间&#xff08;默认的结束时间为99…

day-35 二叉树的右视图

思路 根据层序遍历的思路。将每一层的最右边元素加入返回序列即可 解题方法 注意&#xff1a;链表删除一个数据后会立即重排&#xff0c;所以删除同一层的节点时&#xff0c;每次都删除第一个节点。 Code /*** Definition for a binary tree node.* public class TreeNode {…

企业智能照明控制系统 为企业实现智能化照明管理

工厂车间传统照明的问题及智能照明系统的优势 谢继东15821713522 一、工厂传统照明存在的问题&#xff1a; 1、工业厂房一般建筑结构高&#xff0c;跨距大。灯具安装悬挂高&#xff0c;照明空间大&#xff0c;灯具回路多&#xff0c;而车间是厂区对照明要求较高的区域&#xf…

Linux学习笔记4---点亮LED灯(汇编裸机)

本系统学习利用的是正点原子的阿尔法mini开发板&#xff0c;本系列的学习笔记也是按照正点原子的教程进行学习&#xff0c;但并不是利用虚拟机进行开发&#xff0c;而是使用Windows下的子系统WSL进行学习。 因为 Cortex-A 芯片一上电 SP 指针还没初始化&#xff0c;C 环境还没准…

Open CASCADE 教程 – AIS:自定义呈现

文章目录 开始 (Getting Started)呈现构建器 (Presentation builders)基元数组 (Primitive arrays)基元外观 (Primitive aspects)二次构建器 (Quadric builders)计算选择 (Computing selection)突出显示选择所有者 (Highlighting selection owner)突出显示的方法 (Highlighting…

[QT] 断点调试

目录 一 设置断点 二 调试窗口信息 2.1 默认窗口 2.2 详细窗口属性 三 调试方法和技巧 一 设置断点 在QtCreator中我们有两种方式添加断点。 用鼠标直接点击代码编辑窗口中的某一行按下F9添加/取消断点(操作的是当前鼠标光标所在的代码行) 二 调试窗口信息 2.1 默认窗…

Linux 信号保存

&#x1f493;博主CSDN主页:麻辣韭菜&#x1f493;   ⏩专栏分类&#xff1a;Linux知识分享⏪   &#x1f69a;代码仓库:Linux代码练习&#x1f69a;   &#x1f339;关注我&#x1faf5;带你学习更多Linux知识   &#x1f51d; 目录 前言 阻塞信号 1. 信号其他相关常见…

MFC的CPen与CBush画图对象使用步骤

在MFC中&#xff0c;CPen和CBrush是两个常用的绘图对象&#xff0c;分别用于定义画笔和画刷&#xff0c;可以用于绘制图形、填充区域等。下面我会详细介绍如何在MFC中使用CPen和CBrush来绘制和填充图形。 使用 CPen 绘制图形&#xff1a; 创建 CPen 对象&#xff1a; 首先&am…