0


【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁

初识生产者消费者模型

举一个例子:
在这里插入图片描述
学生去超市消费的时候,与厂家生产的时候,两者互不相冲突。
生产的过程与消费的过程 – 解耦
临时的保存产品的场所(超时) – 缓冲区

在这里插入图片描述
模型总结“321”原则:

  • 3种关系:生产者和生产者(互斥),消费者和消费者(互斥),生产者和消费者(互斥[保证共享资源的安全性] && 同步) – 产品(数据)
  • 2种角色:生产者线程,消费者线程
  • 1个交易场所:一段特定结构的缓冲区

只要我们想写生产消费模型,我们本质工作其实就是维护321原则!

特点:

  1. 生产线程和消费线程进行解耦
  2. 支持生产和消费的一段时间的忙闲不均的问题
  3. 提高效率

举例:
我们以前:main函数获取用户输入,然后调用fun函数,fun函数执行,打印结果。这是一个串行的流程,现在我们对应生产者消费者模型:
在这里插入图片描述


同步

条件变量

在这里插入图片描述
由于上述的原因,我们不能仅靠锁来去实现线程互斥。

什么是条件变量:

  • 当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。(比如我们抢票,如果目前还没有票,那么每个人都是做同样的动作:加锁→判断票是否为0→解锁,在还没有放票之前,每个人都什么也做不了)
  • 例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

感性认识条件变量:
模拟一个面试流程:
1.假如我们一堆人都要到一家公司面试,但是这个公司的HR组织的不行,每次面试都是一堆人举手来竞争面试机会,都说要自己先来,而HR也是看谁顺眼就让他进去面试 – 一份共享资源被多线程并发式的竞争
2.同样是面试,而HR组织的好,说要面试必须要到等待区等待,按照排队顺序来:(这个等待区就是一个条件变量)
在这里插入图片描述

当条件不满足的时候,我们线程必须去某些定义好的条件变量上进行等待!

初步使用

#include<iostream>#include<string>#include<unistd.h>#include<pthread.h>int tickets =1000;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;void*start_routine(void*args){
    std::string name =static_cast<constchar*>(args);while(true){pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex);// 为什么要有mutex,后面就说// 判断暂时省略
        std::cout << name <<" -> "<< tickets << std::endl;
        tickets--;pthread_mutex_unlock(&mutex);}}intmain(){// 通过条件变量控制线程的执行
    pthread_t t[5];for(int i =0; i <5; i++){char*name =newchar[64];snprintf(name,64,"thread %d", i +1);pthread_create(t+i,nullptr, start_routine, name);}while(true){sleep(1);pthread_cond_signal(&cond);//pthread_cond_broadcast(&cond);
        std::cout <<"main thread wakeup one thread..."<< std::endl;}for(int i =0; i <5; i++){pthread_join(t[i],nullptr);}return0;}

在这里插入图片描述
我们可以发现进程按照一定的顺序在执行

在这里插入图片描述


POSIX信号量

POSIX

信号量和

SystemV

信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。

初始化信号量

#include<semaphore.h>intsem_init(sem_t*sem,int pshared,unsignedint value);
参数:
pshared:0表示线程间共享,非零表示进程间共享
value:信号量初始值

销毁信号量

intsem_destroy(sem_t*sem);

等待信号量

功能:等待信号量,会将信号量的值减1intsem_wait(sem_t*sem);//P()

发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。
intsem_post(sem_t*sem);//V()

其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
  • CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。

自旋锁

场景一:你喊你的朋友一起去吃饭,你的朋友说等他一会就来,在等的期间你每隔一会就打电话问他来没来 – 自旋
场景二:你喊你的朋友一起去吃饭,你的朋友说等他一会就来,你不想就只在原地等,你走路去学校网吧上网,走路去的过程叫做挂起,在网吧上网叫做等待,你朋友跟你打电话说他已经来了,然后你回学校这叫做唤醒。-- 挂起等待

是什么决定了最终的等待方式?
等待的时间问题。已经被申请的临界资源决定了其他线程要等待,一个成功申请临界资源的线程在临界区内要待多少时间,这个时间长短就决定了我们使用哪种方式:(阻塞等待/自旋)。时间的长短如何定义?都是用分别测试效果。

在这里插入图片描述

intpthread_spin_lock(pthread_spinlock_t*lock);intpthread_spin_trylock(pthread_spinlock_t*lock);

在这里插入图片描述

intpthread_spin_destroy(pthread_spinlock_t*lock);intpthread_spin_init(pthread_spinlock_t*lock,int pshared);

在这里插入图片描述

intpthread_spin_unlock(pthread_spinlock_t*lock);

读写锁

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。

场景:比如在以前学校的黑板报,画黑板报的为写者,写者与写者之间是互斥关系(你正在写字,他不能把你写的字擦了画画),而观看的同学是读者,读者与读者之间不存在什么关系(黑板报画好了,我们都是一起看的,不存在先来就先看其余的蒙着眼睛不准看)。

读者写者模型与生产者消费者模型本质区别是:消费者会拿走数据而读者不会。
读者写者:写者之间 – 互斥 、读写者 – 互斥/同步 、读者之间 – 没关系
读者写者模型适用场景:一次发布,很长时间不做修改,大部分时间都是被读取的(大部分时间都是被读写,少量的时间在进行写入)
在这里插入图片描述

设置读写优先

intpthread_rwlockattr_setkind_np(pthread_rwlockattr_t*attr,int pref);
pref 共有 3 种选择
PTHREAD_RWLOCK_PREFER_READER_NP(默认设置) 读者优先,可能会导致写者饥饿情况
PTHREAD_RWLOCK_PREFER_WRITER_NP 写者优先,目前有 BUG,导致表现行为和
PTHREAD_RWLOCK_PREFER_READER_NP 一致
PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写者优先,但写者不能递归加锁

在这里插入图片描述

intpthread_rwlock_destroy(pthread_rwlock_t*rwlock);intpthread_rwlock_init(pthread_rwlock_t*restrict rwlock,constpthread_rwlockattr_t*restrict attr);

读者加锁:
在这里插入图片描述

intpthread_rwlock_rdlock(pthread_rwlock_t*rwlock);intpthread_rwlock_tryrdlock(pthread_rwlock_t*rwlock);

写者加锁:
在这里插入图片描述

intpthread_rwlock_trywrlock(pthread_rwlock_t*rwlock);intpthread_rwlock_wrlock(pthread_rwlock_t*rwlock);

统一解锁:
在这里插入图片描述

intpthread_rwlock_unlock(pthread_rwlock_t*rwlock);

在这里插入图片描述


如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀


本文转载自: https://blog.csdn.net/DEXTERFUTIAN/article/details/131923312
版权归原作者 侠客cheems 所有, 如有侵权,请联系我们删除。

“【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁”的评论:

还没有评论