linux:线程同步

这篇具有很好参考价值的文章主要介绍了linux:线程同步。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

linux:线程同步,Linux,linux,运维

个人主页 : 个人主页
个人专栏 : 《数据结构》 《C语言》《C++》《Linux》


前言

本文作为我对于线程同步知识总结


线程同步

  • 同步:在保证数据安全的前提下,让线程能够按照某种顺序访问临界资源,从而有效避免饥饿问题,叫做同步
  • 竞态条件:是指多个线程同时访问系统共享资源时,由于其执行顺序或时间上的不确定性,导致数据不一致或其它不可预料的结果(一般发生在对称多处理环境,中断和异常处理,内核态抢占,并发执行…)

看了上面两个概念,你可能还是不太理解同步是什么,为什么要有同步。下面我们就举一个例子。
我们假定有两个人,一个人A将苹果放在桌子上,另一个人B蒙着眼睛去桌子上拿苹果,桌子每次只允许有一个人,因为B不清楚桌子上的情况是什么(有一个苹果,没有苹果,桌子上全是苹果),那为了保险起见,B只能疯狂的去桌子上拿苹果,以保证B拿到所有的苹果。那如果桌子上没有苹果,B仍然疯狂去桌子上拿苹果,此时A是不是就不能去桌子上放苹果,那此时B是不是再做无用工,而且导致了A不能放苹果,B拿苹果的效率降低。如果我们将A,B换成线程,苹果看出共享资源(某种任务),桌子看成临界区,那B是不是就是一直在做申请锁,再释放锁的无效工作,并且导致了A的饥饿问题。这时我们就需要保证A,B之间的顺序问题,如在B访问过桌子后,不能立即再次访问桌子,要等待A访问桌子后。这是不是就会使A,B拿苹果的效率提升。而这就是为什么要有同步的理由

条件变量接口

初始化条件变量

  • 静态初始化linux:线程同步,Linux,linux,运维
    与互斥锁类似,定义一个全局的条件变量,用PTHREAD_COND_INITIALIZER宏来初始化,系统自动释放该条件变量。该宏一般存放在 /usr/include/pthread.h 路径下
    linux:线程同步,Linux,linux,运维
  • 动态初始化
    linux:线程同步,Linux,linux,运维
    restrict是C语言的一个关键字,表示该指针是唯一的访问其指向对象的指针。
    cond将被初始化的条件变量,attr 为nullptr,使用默认属性初始化条件变量。
    如果函数成功执行,返回0; 如果函数执行失败,则返回错误码,如EAGAIN(资源暂时不可用),ENOMEM(内存不足)

销毁条件变量
linux:线程同步,Linux,linux,运维
cond 表示将被销毁的条件变量,需要注意在调用pthread_cond_destroy后,该指针本身并未被销毁,只是所指向的条件变量被销毁,记得将指针置空
如果函数成功执行,返回0; 如果函数失败,返回错误码。如EBUSY,该条件变量正在被使用


等待条件变量
linux:线程同步,Linux,linux,运维
cond 表示将要等待的条件变量,mutex 表示线程所持有的互斥锁
如果函数成功执行,返回0;如果函数执行失败,返回错误码,如EINVAL 无效的参数(条件变量 or 互斥锁为初始化…)

关于该函数的参数为什么会有锁,有什么注意事项,在下面代码示例,我们再解释。


唤醒等待

linux:线程同步,Linux,linux,运维
cond要发生信号的条件变量指针。
如果函数执行成功,返回0; 如果函数执行失败,返回错误码,如ENVAL(无效的参数)
需要注意的是,pthread_cond_signal只用于唤醒在cond条件变量的阻塞队列中等待的一个线程。

linux:线程同步,Linux,linux,运维
该函数的参数与返回值与pthread_cond_signal相同,只不过该函数唤醒所有在cond条件变量的阻塞队列中等待的所有线程,而那些线程先执行,取决于操作系统的调度策略。


简单示例

我们先来看看下面代码,3个线程争夺ticket资源。当三个线程检测到ticket == 0时,三个线程都将等待。我们主线程每过5秒,使ticket += 10。

#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>

using namespace std;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ticket = 100;

void* threadRoutine(void *args)
{
    const string threadname = static_cast<char*>(args);
    
    while(true)
    {   
        usleep(1000);
        pthread_mutex_lock(&mutex);
        if(ticket > 0)
        {
            ticket--;
            cout << threadname << ", get a ticket: " << ticket << endl;
        }
        else
        {
            cout << threadname << ", ticket == 0" << endl;
            pthread_cond_wait(&cond, &mutex);
        }
        pthread_mutex_unlock(&mutex);
    }

    return nullptr;
}

int main()
{
    pthread_t td1;
    pthread_create(&td1, nullptr, threadRoutine, (void*)"thread-1");

    pthread_t td2;
    pthread_create(&td2, nullptr, threadRoutine, (void*)"thread-2");

    pthread_t td3;
    pthread_create(&td3, nullptr, threadRoutine, (void*)"thread-3");
    
    while(true)
    {
        sleep(5);
        pthread_mutex_lock(&mutex);
        ticket += 10;
        pthread_mutex_unlock(&mutex);

        pthread_cond_signal(&cond);
    }

    pthread_join(td1, nullptr);
    pthread_join(td2, nullptr);
    pthread_join(td3, nullptr);
    return 0;
}

linux:线程同步,Linux,linux,运维
当线程2,线程3,线程1先后检测到ticket == 0时,在cond的等待队列中,线程以2,3,1的顺序排队。那当调用pthread_cond_signal函数时,线程2会先执行,执行完再检测到ticket==0,排到等待队列尾部
linux:线程同步,Linux,linux,运维
再调用pthread_cond_signal函数时,线程3会执行。
linux:线程同步,Linux,linux,运维
再调用pthread_cond_signal函数时,线程1会执行。
linux:线程同步,Linux,linux,运维
这样,我们多线程就可以按某种特定顺序来执行。
那如果我们使用pthread_cond_broadcast函数,会有什么情况?
linux:线程同步,Linux,linux,运维
我们会发现,三个线程都被唤醒,来争抢ticket资源,其先后顺序由操作系统决定(优先级,竞争锁的能力…)。


pthread_cond_wait为什么要有mutex

此时不知道你是否有一个疑问,我们假定线程-1先争抢到ticket,检测到ticket == 0时,该线程-1要在cond的等待队列中等待,然后其它两个线程在进入临界区,检测到ticket==0,在等待队列中排队。但是线程-1是持有锁进入等待队列的,那其它两个线程是如何进入临界区的?答案很明显,那就是线程-1在cond等待中,一定释放了其持有的锁,从而使其余两个线程可以持有锁进入临界区。这就是为什么pthread_cond_wait函数的参数要有互斥锁的存在。
那我们在深入想一想,调用pthread_cond_wait函数后,该线程是先释放锁,再进入等待队列中;还是先进入等待队列中,再释放锁?答案是都不是。释放锁和进入等待队列是同时进行的!!!这也表示pthread_cond_wait函数是原子的,在调用pthread_cond_wait时,涉及的互斥锁释放和进入等待队列的操作是作为一个不可分割的整体来执行。确保了线程在调用该函数时不会遇到竞态条件,即线程在释放锁和进入等待队列之间不会被其它线程打断。
以下是线程调用pthread_cond_wait的过程

  1. 线程必须已经持有锁:在调用pthread_cond_wait时,线程必须已经锁定了某个互斥锁,这是该函数的前提条件

  2. 自动释放互斥锁:当线程调用pthread_cond_wait时,它会自动释放它当前持有的互斥锁。这一步是为了允许其它线程有机会获取该互斥锁并修改共享资源,从而可能改变条件变量的状态

  3. 加入等待队列:释放互斥锁之后,线程会接着被添加到条件变量的等待队列中,并在此处等待

  4. 等待被唤醒:线程在等待队列中等待,直到其它线程调用pthread_cond_signal 或 pthread_cond_broadcast来唤醒它

  5. 重新获取互斥锁:当线程被唤醒时,线程会尝试重新获取之前释放的互斥锁。如果锁此时没有被其它线程持有,线程将成功获取锁并继续执行。如果锁仍然被其它线程持有,线程将阻塞(在申请锁的地方阻塞),直到能够获取锁时。

具体来说,pthread_cond_wait函数的内部实现保证了2,3步骤的原子性。


伪唤醒问题的解决 (if->while)

伪唤醒问题是指:在多线程环境中,当线程等待某个条件变量时,它可能会在没有到达预期的情况下被唤醒。
对于这一问题,我们可以在判断的时候,将if 变为 while,使其被唤醒后任然进行条件判断,如果条件满足,线程继续向后执行,如果条件不被满足,线程继续在该条件变量下等待。

pthread_mutex_lock(&mutex);
// 访问临界区 
while(条件为假)
	pthread_cond_wait(&cond, &mutex);
pthread_mutex_unlock(&mutex);

总结

以上就是我对于线程同步的总结。

linux:线程同步,Linux,linux,运维文章来源地址https://www.toymoban.com/news/detail-854918.html

到了这里,关于linux:线程同步的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • Linux和windows进程同步与线程同步那些事儿(五):Linux下进程同步

    Linux和windows进程同步与线程同步那些事儿(一) Linux和windows进程同步与线程同步那些事儿(二): windows线程同步详解示例 Linux和windows进程同步与线程同步那些事儿(三): Linux线程同步详解示例 Linux和windows进程同步与线程同步那些事儿(四):windows 下进程同步 Linux和wi

    2024年02月02日
    浏览(39)
  • Linux--线程-条件控制实现线程的同步

    1.条件变量 条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。 条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之

    2024年02月05日
    浏览(39)
  • 【Linux】线程同步

    初始化条件变量-pthread_cond_init 初始化条件变量的函数叫做 pthread_cond_init 参数说明 cond:需要初始化的条件变量 attr:初始化条件变量的属性,一般设置为NULL 返回值说明 初始化成功返回0,失败返回错误码 注意:调用 pthread_cond_init 函数初始化条件变量叫做动态分配,我们还可以用静

    2024年02月07日
    浏览(33)
  • Linux->线程同步

    前言: 1 线程同步引入 2 条件变量 2.1 线程饥饿 2.2 条件变量接口 2.3 添加条件变量 3 生产者和消费者模型         本篇主要讲解了关于线程同步的相关知识,还有生产者和消费者模型的认识和使用。         在讲解线程同步之前,我们先来看一下当一个程序之中只有线程互

    2024年02月11日
    浏览(32)
  • 【Linux】线程同步和互斥

    1.临界资源:多线程执行流共享的资源,且一次只能允许一个执行流访问的资源就叫做临界资源。(多线程、多进程打印数据) 2.临界区:每个线程内部,访问临界资源的代码,就叫做临界区。 3.互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对

    2024年02月08日
    浏览(46)
  • Linux线程同步实例

    生产者消费者模型是一种常用的并发设计模式,它可以解决生产者和消费者之间的速度不匹配、解耦、异步等问题。生产者消费者模型的应用场景有很多,例如Excutor任务执行框架、消息中间件activeMQ、任务的处理时间比较长的情况下等。 生产者消费者模型的基本结构如下 :

    2024年02月07日
    浏览(40)
  • linux:线程同步

    个人主页 : 个人主页 个人专栏 : 《数据结构》 《C语言》《C++》《Linux》 本文作为我对于线程同步知识总结 同步:在保证数据安全的前提下,让线程能够按照某种顺序访问临界资源,从而有效避免饥饿问题,叫做同步 竞态条件:是指多个线程同时访问系统共享资源时,由

    2024年04月17日
    浏览(24)
  • 【Linux】多线程2——线程互斥与同步/多线程应用

    💭上文主要介绍了多线程之间的独立资源,本文将详细介绍多线程之间的 共享资源 存在的问题和解决方法。 intro 多线程共享进程地址空间,包括创建的全局变量、堆、动态库等。下面是基于全局变量实现的一个多线程抢票的demo。 发现错误:线程抢到负数编号的票,为什么

    2024年02月10日
    浏览(42)
  • Linux——线程的同步与互斥

    目录 模拟抢火车票的过程 代码示例 thread.cc Thread.hpp 运行结果 分析原因 tickets减到-2的本质  解决抢票出错的方案 临界资源的概念 原子性的概念 加锁 定义 初始化 销毁 代码形式如下 代码示例1: 代码示例2: 总结 如何看待锁 申请失败将会阻塞  pthread_mutex_tyrlock 互斥锁实现

    2024年02月06日
    浏览(42)
  • 【Linux】多线程互斥与同步

    互斥 指的是一种机制,用于确保在同一时刻只有一个进程或线程能够访问共享资源或执行临界区代码。 互斥的目的是 防止多个并发执行的进程或线程访问共享资源时产生竞争条件,从而保证数据的一致性和正确性 ,下面我们来使用多线程来模拟实现一个抢票的场景,看看所

    2024年02月09日
    浏览(39)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包