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

这篇具有很好参考价值的文章主要介绍了【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

初识生产者消费者模型

举一个例子:
【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端
学生去超市消费的时候,与厂家生产的时候,两者互不相冲突。
生产的过程与消费的过程 – 解耦
临时的保存产品的场所(超时) – 缓冲区

【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端
模型总结“321”原则:

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

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

特点:

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

举例:
我们以前:main函数获取用户输入,然后调用fun函数,fun函数执行,打印结果。这是一个串行的流程,现在我们对应生产者消费者模型:
【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端


同步

条件变量

【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端
由于上述的原因,我们不能仅靠锁来去实现线程互斥。

什么是条件变量:

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

感性认识条件变量:
模拟一个面试流程:
1.假如我们一堆人都要到一家公司面试,但是这个公司的HR组织的不行,每次面试都是一堆人举手来竞争面试机会,都说要自己先来,而HR也是看谁顺眼就让他进去面试 – 一份共享资源被多线程并发式的竞争
2.同样是面试,而HR组织的好,说要面试必须要到等待区等待,按照排队顺序来:(这个等待区就是一个条件变量)
【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端

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

初步使用

#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<const char *>(args);
    while (true)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex); // 为什么要有mutex,后面就说
        // 判断暂时省略
        std::cout << name << " -> " << tickets << std::endl;
        tickets--;
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    // 通过条件变量控制线程的执行
    pthread_t t[5];
    for (int i = 0; i < 5; i++)
    {
        char *name = new char[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);
    }

    return 0;
}

【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端
我们可以发现进程按照一定的顺序在执行

【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端


POSIX信号量

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

初始化信号量

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

销毁信号量

int sem_destroy(sem_t *sem);

等待信号量

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

发布信号量

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

其他常见的各种锁

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

自旋锁

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

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

【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端

int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);

【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端

int pthread_spin_destroy(pthread_spinlock_t *lock);
int pthread_spin_init(pthread_spinlock_t *lock, int pshared);

【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端

int pthread_spin_unlock(pthread_spinlock_t *lock);

读写锁

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

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

读者写者模型与生产者消费者模型本质区别是:消费者会拿走数据而读者不会。
读者写者:写者之间 – 互斥 、读写者 – 互斥/同步 、读者之间 – 没关系
读者写者模型适用场景:一次发布,很长时间不做修改,大部分时间都是被读取的(大部分时间都是被读写,少量的时间在进行写入)
【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端

设置读写优先

int pthread_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 写者优先,但写者不能递归加锁

【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
              const pthread_rwlockattr_t *restrict attr);

读者加锁:
【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

写者加锁:
【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

统一解锁:
【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁,Linux系统基础,linux,运维,线程同步,条件变量,生产者消费者,后端


如有错误或者不清楚的地方欢迎私信或者评论指出🚀🚀文章来源地址https://www.toymoban.com/news/detail-624671.html

到了这里,关于【Linux】线程同步 -- 条件变量 | 生产者消费者模型 | 自旋锁 |读写锁的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 线程同步--生产者消费者模型

    条件变量是 线程间共享的全局变量 ,线程间可以通过条件变量进行同步控制 条件变量的使用必须依赖于互斥锁以确保线程安全,线程申请了互斥锁后,可以调用特定函数 进入条件变量等待队列(同时释放互斥锁) ,其他线程则可以通过条件变量在特定的条件下唤醒该线程( 唤醒后线

    2024年01月19日
    浏览(28)
  • 从生产者-消费者模型中学习互斥量,锁,条件变量

    经典的并发控制模型 主要是练习 mutex unique_lock conditional_variable [[20 原子操作]] 1 mutex 互斥量 mutex 是一种互斥的同步原语,用于保护共享资源的访问,确保在同一时间只有一个线程可以访问共享资源。通过对互斥量加锁和解锁,可以实现对共享资源的独占访问。 2 shared_mutex 共

    2024年02月06日
    浏览(28)
  • 线程同步--生产者消费者模型--单例模式线程池

    条件变量是 线程间共享的全局变量 ,线程间可以通过条件变量进行同步控制 条件变量的使用必须依赖于互斥锁以确保线程安全,线程申请了互斥锁后,可以调用特定函数 进入条件变量等待队列(同时释放互斥锁) ,其他线程则可以通过条件变量在特定的条件下唤醒该线程( 唤醒后线

    2024年01月20日
    浏览(33)
  • 12.3用信号量进行线程同步——生产者与消费者问题

    1.shell程序设计 2.内存管理 3.链接库 4.文件操作

    2024年02月04日
    浏览(30)
  • 【Linux】线程安全-生产者消费者模型

    1个线程安全的队列:只要保证先进先出特性的数据结构都可以称为队列 这个队列要保证互斥(就是保证当前只有一个线程对队列进行操作,其他线程不可以同时来操作),还要保证同步,当生产者将队列中填充满了之后要通知消费者来进行消费,消费者消费之后通知生产者

    2024年02月10日
    浏览(31)
  • 线程池-手写线程池Linux C简单版本(生产者-消费者模型)

    本线程池采用C语言实现 线程池的场景: 当某些任务特别耗时(例如大量的IO读写操作),严重影响线程其他的任务的执行,可以使用线程池 线程池的一般特点: 线程池通常是一个生产者-消费者模型 生产者线程用于发布任务,任务通常保存在任务队列中 线程池作为消费者,

    2024年02月14日
    浏览(32)
  • 『Linux』第九讲:Linux多线程详解(四)_ 生产者消费者模型

    「前言」文章是关于Linux多线程方面的知识,上一篇是 Linux多线程详解(三),今天这篇是 Linux多线程详解(四),内容大致是生产消费者模型,讲解下面开始! 「归属专栏」Linux系统编程 「主页链接」个人主页 「笔者」枫叶先生(fy) 「枫叶先生有点文青病」「每篇一句」

    2024年02月07日
    浏览(33)
  • Linux之【多线程】生产者与消费者模型&BlockQueue(阻塞队列)

    举个例子:学生要买东西,一般情况下都会直接联系厂商,因为买的商品不多,对于供货商来说交易成本太高,所以有了交易场所超市这个媒介的存在。目的就是为了集中需求,分发产品。 消费者与生产者之间通过了超市进行交易。当生产者不需要的时候,厂商可以继续生产

    2024年02月02日
    浏览(28)
  • 【Linux】生产者消费者模型:基于阻塞队列和环形队列 | 单例模式线程池

    死锁是指在一组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站用不会释放的资源而处于的一种永久等待状态。 当多线程并发执行并都需要访问临界资源时,因为每个线程都是不同的执行流,这就有可能 导致数据不一致问题 ,为了避免此问题的发生

    2024年01月24日
    浏览(30)
  • 【Linux学习】多线程——信号量 | 基于环形队列的生产者消费者模型 | 自旋锁 | 读写锁

    🐱作者:一只大喵咪1201 🐱专栏:《Linux学习》 🔥格言: 你只管努力,剩下的交给时间! 之前在学习进程间通信的时候,本喵简单的介绍过一下信号量,今天在这里进行详细的介绍。 这是之前写的基于阻塞队列的生产者消费者模型中向阻塞队列中push任务的代码。 上面代码

    2024年02月07日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包