Linux下的多线程编程:原理、工具及应用(3)

这篇具有很好参考价值的文章主要介绍了Linux下的多线程编程:原理、工具及应用(3)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Linux下的多线程编程:原理、工具及应用(3),Linux练功 初阶功法,linux,运维,后端,服务器

                                               🎬慕斯主页:修仙—别有洞天

                                              ♈️今日夜电波:Flower of Life—陽花

                                                                0:34━━━━━━️💟──────── 4:46
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

条件变量再理解

pthread_cond_t

PTHREAD_COND_INITIALIZER

pthread_cond_init()

pthread_cond_destroy()

pthread_cond_wait()

pthread_cond_signal()

pthread_cond_broadcast()

示例代码

生产者消费者模型再理解

BlockingQueue 概念

BlockingQueue 的实现

示例代码

进一步封装


        从前面的理解中,我们对于死锁和条件变量有了一定程度的了解。对此,下面继续对于死锁和条件变量的共同作用加深理解。

条件变量再理解

pthread_cond_t

    pthread_cond_t是POSIX线程库中用于线程同步和通信的一种条件变量类型。它允许一个或多个线程等待直到其他线程通过信号通知特定条件已满足,从而实现线程间的协调工作。以下是关于pthread_cond_t的详细说明:

  1. 初始化:在开始使用pthread_cond_t之前,需要对其进行初始化。可以通过静态或动态的方式初始化条件变量。静态初始化通常在声明时直接赋予PTHREAD_COND_INITIALIZER值。如果选择动态初始化,则需要调用pthread_cond_init函数。
  2. 等待与唤醒机制:线程在等待某个条件变量时会进入睡眠状态,并释放其持有的互斥锁,这样其他线程可以执行相应的条件改变操作。当条件满足后,其他线程将通过pthread_cond_signalpthread_cond_broadcast函数唤醒等待该条件的线程。被唤醒的线程在返回前通常会再次获得互斥锁,以确保同步访问共享资源。
  3. 配合互斥锁使用:条件变量通常与互斥锁一起使用。线程在等待条件变量之前必须先锁定互斥锁,并在调用pthread_cond_wait之后解锁,以便其他线程可以访问共享资源并修改条件。在从pthread_cond_wait返回之前,线程会重新锁定互斥锁,以继续其工作。
  4. 销毁:当条件变量不再使用时,应调用pthread_cond_destroy函数进行清理,以避免资源泄露。
  5. 注意事项:在使用条件变量时要注意避免竞态条件和死锁,确保在检查条件和调用等待函数之间的操作是原子性的。
  6. 用途举例:条件变量常用于生产者-消费者问题、读写锁实现等多线程同步场景。

PTHREAD_COND_INITIALIZER

    PTHREAD_COND_INITIALIZER是POSIX线程库中用于初始化条件变量的宏。它的作用是将条件变量初始化为一个已定义的状态,以便在后续使用中进行比较和操作。

        具体来说,PTHREAD_COND_INITIALIZER是一个静态初始化器,可以在声明条件变量时直接将其赋值给条件变量。这个宏会将条件变量的内存设置为0,表示该条件变量尚未被初始化。

        以下是使用PTHREAD_COND_INITIALIZER进行条件变量初始化的示例代码:

#include <pthread.h>

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

        在这个例子中,我们声明了一个名为cond的条件变量,并使用PTHREAD_COND_INITIALIZER将其初始化为0。这样,我们就可以在后续的代码中使用cond来进行线程同步和通信的操作了。

        需要注意的是,PTHREAD_COND_INITIALIZER只能用于静态初始化,不能用于动态初始化。如果需要在运行时动态创建条件变量,需要使用pthread_cond_init()函数进行初始化。

pthread_cond_init()

    pthread_cond_init()是POSIX线程库中的一个函数,用于初始化条件变量。条件变量是一种同步机制,允许线程等待某个条件满足后再继续执行。

        该函数的原型如下:

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

参数说明:

  • cond:指向要初始化的条件变量的指针。
  • attr:指向条件变量属性对象的指针,可以设置为NULL表示使用默认属性。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • 在使用完条件变量后,需要调用pthread_cond_destroy()函数进行销毁,以释放相关资源。
  • 如果使用了自定义的属性对象,也需要在适当的时候调用pthread_condattr_destroy()函数进行销毁。

pthread_cond_destroy()

    pthread_cond_destroy()是POSIX线程库中的一个函数,用于销毁条件变量

        该函数的原型如下:

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);

参数说明:

  • cond:指向要销毁的条件变量的指针。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • 在使用完条件变量后,需要调用pthread_cond_destroy()函数进行销毁,以释放相关资源。
  • 如果条件变量正在被其他线程等待,则无法销毁该条件变量,直到所有等待该条件的线程已经返回。

pthread_cond_wait()

    pthread_cond_wait()是POSIX线程库中的一个函数,用于等待条件变量满足

        该函数的原型如下:

#include <pthread.h>

int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

参数说明:

  • cond:指向要等待的条件变量的指针。
  • mutex:指向与条件变量关联的互斥锁的指针。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • pthread_cond_wait()函数会自动释放传入的互斥锁,并使当前线程进入阻塞状态,直到其他线程调用pthread_cond_signal()pthread_cond_broadcast()函数唤醒该线程。
  • 在调用pthread_cond_wait()函数之前,必须先加锁互斥锁,否则会导致未定义的行为。

pthread_cond_signal()

    pthread_cond_signal()是POSIX线程库中的一个函数,用于唤醒等待在条件变量上的一个线程。

        该函数的原型如下:

int pthread_cond_signal(pthread_cond_t *cond);

参数说明:

  • cond:指向要操作的条件变量的指针。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • pthread_cond_signal()函数只会唤醒等待在条件变量上的一个线程,如果有多个线程在等待,其他线程将继续等待。
  • 如果当前没有线程在等待条件变量,pthread_cond_signal()函数的行为是未定义的。
  • 在多线程编程中,通常需要结合互斥锁和条件变量来实现同步,确保线程安全。

pthread_cond_broadcast()

    pthread_cond_broadcast()是POSIX线程库中的一个函数,用于唤醒等待在条件变量上的所有线程。

        该函数的原型如下:

int pthread_cond_broadcast(pthread_cond_t *cond);

参数说明:

  • cond:指向要操作的条件变量的指针。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • pthread_cond_broadcast()函数会唤醒等待在条件变量上的所有线程,而不仅仅是一个线程。如果有多个线程在等待,它们都将被唤醒并继续执行。
  • 如果当前没有线程在等待条件变量,pthread_cond_broadcast()函数的行为是未定义的。
  • 在多线程编程中,通常需要结合互斥锁和条件变量来实现同步,确保线程安全。

示例代码

        使用互斥锁与条件等待来使得代码高效运行,以防某个线程一直占用锁从而占用资源!

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

pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex=PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;

int tickets=1000;

void *threadRoutine(void *args)
{
    std::string name=static_cast<const char*>(args);

    while(true)
    {
        pthread_mutex_lock(&mutex);
        if(tickets>0)
        {
            std::cout << name<< ", get a ticket: " << tickets-- << std::endl; // 模拟抢票
            usleep(1000);
        }else 
        {
            std::cout << "没有票了," << name << std::endl;
           // 1. 让线程在进行等待的时候,会自动释放锁 
           // 2. 线程被唤醒的时候,是在临界区内唤醒的,当线程被唤醒, 线程在pthread_cond_wait返回的时候,要重新申请并持有锁
           // 3. 当线程被唤醒的时候,重新申请并持有锁本质是也要参与锁的竞争的!!
            pthread_cond_wait(&cond,&mutex);
        }

        pthread_mutex_unlock(&mutex);

    }
}

int main()
{
    //child pthread
    pthread_t t1,t2,t3;
    pthread_create(&t1,nullptr,threadRoutine,(void*)"thread-1");
    pthread_create(&t2,nullptr,threadRoutine,(void*)"thread-2");
    pthread_create(&t3,nullptr,threadRoutine,(void*)"thread-3");

    //main pthread

    while(true)
    {

        sleep(5);
        pthread_mutex_lock(&mutex);
        tickets+=1000;
        pthread_mutex_unlock(&mutex);
        pthread_cond_broadcast(&cond);//给全部发信号
        //pthread_cond_signal(&cond);//给其中一个发信号
    }

        pthread_join(t1,nullptr);
        pthread_join(t1,nullptr);
        pthread_join(t1,nullptr);

        return 0;
    
}

Linux下的多线程编程:原理、工具及应用(3),Linux练功 初阶功法,linux,运维,后端,服务器

生产者消费者模型再理解

BlockingQueue 概念

        在多线程编程中阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进程操作时会被阻塞) 大致图解:

Linux下的多线程编程:原理、工具及应用(3),Linux练功 初阶功法,linux,运维,后端,服务器

BlockingQueue 的实现

        主要是基于还是基于库中queue来进行包装,push作为生产者的生产操作,而pop作为消费者的消费操作其中的细节:当queue到达我们设定的满队列值时,需要根据条件变量来等待,而发生这个信号在消费者的pop函数。相对的pop函数中如果队列为空了,那么也需要等待push函数中的信号

BlockQueue.hpp

#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>

const int defaultcap = 5;//首先默认队列大小为5

template <class T>
class BlockQueue
{
public:
    BlockQueue(int cap = defaultcap)
        : _capacity(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_p_cond, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
    }

    bool IsFull()
    {
        return _q.size() == _capacity;
    }

    bool IsEmpty()
    {
        return _q.size() == 0;
    }

    void Push(const T &in) // 生产者生产
    {
        pthread_mutex_lock(&_mutex);

    //本来使用的是if,但是为了防止pthread_cond_wait伪唤醒从而使用while
        while (IsFull()) // 写出来的代码,具有较强的鲁棒、健壮性
        {
            // 阻塞等待
            pthread_cond_wait(&_p_cond, &_mutex); // 1. 关于pthread_cond_wait在进一步理解!
        }
        _q.push(in);
        // if(_q.size() > _productor_water_line) pthread_cond_signal(&_c_cond);
        pthread_cond_signal(&_c_cond);
        pthread_mutex_unlock(&_mutex);
    }

    void Pop(T *out) // 消费者的
    {
    
        pthread_mutex_lock(&_mutex);
        while (IsEmpty())
        {
            // 阻塞等待
            pthread_cond_wait(&_c_cond, &_mutex);
        }

        *out = _q.front();
        _q.pop();
        //if(_q.size() < _consumer_water_line) pthread_cond_signal(&_p_cond);
        pthread_cond_signal(&_p_cond);
        pthread_mutex_unlock(&_mutex);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
    }

private:
    std::queue<T> _q;
    int _capacity;
    pthread_mutex_t _mutex; // 锁
    pthread_cond_t _p_cond; // 生产者条件
    pthread_cond_t _c_cond; // 消费者的条件

};

示例代码

        需要特别注意其中的sleep,如果在消费者函数或者生产者函数中表示为另外一方先行执行!但是根据上面我们push和pop函数的相互等待条件。如果是消费者先执行,那么他会等待生产者生产,每生产一个就消费一个。而如果生产者先执行,则会在一瞬间生产很多,而后消费者每消费一个,生产者生产一个。

#include "BlockQueue.hpp"
#include <pthread.h>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>

void *consumer(void *args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);

    while (true)
    {
        int data;

        //sleep(1);
        
        bq->Pop(&data);

        std::cout << "consumer data: " << data << std::endl;
    }

    return nullptr;
}

void *productor(void *args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);
    while (true)
    {
        int data = rand() % 10;
        std::cout << "productor task: " << data << std::endl;
        bq->Push(data);

        sleep(1);
    }
}

int main()
{
    srand((uint16_t)time(nullptr) ^ getpid() ^ pthread_self()); // 只是为了形成更随机的数据
    BlockQueue<int> *bq = new BlockQueue<int>();
    pthread_t c, p; // 消费者和生产者

    pthread_create(&c, nullptr, consumer, bq);
    pthread_create(&p, nullptr, productor, bq);

    pthread_join(c, nullptr);
    pthread_join(p, nullptr);
    
    return 0;
}

进一步封装

        进一步封装,延续第一篇的代码,把锁也一同封装了!让代码更加简洁!

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;
};

BlockQueue.hpp

#pragma  once

#include <iostream>
#include <queue>
#include <pthread.h>
#include "LockGuard.hpp"

const int defaultcap = 5; // for test

template<class T>
class BlockQueue
{
public:
    BlockQueue(int cap = defaultcap):_capacity(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_p_cond, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
    }
    bool IsFull()
    {
        return _q.size() == _capacity;
    }
    bool IsEmpty()
    {
        return _q.size() == 0;
    }
    void Push(const T &in) // 生产者的
    {
        LockGuard lockguard(&_mutex);
        // pthread_mutex_lock(&_mutex); // 2. lockguard 3. 重新理解生产消费模型(代码+理论) 4. 代码整体改成多生产,多消费
        while(IsFull()) // 写出来的代码,具有较强的鲁棒、健壮性
        {
            // 阻塞等待
            pthread_cond_wait(&_p_cond, &_mutex); // 1. 关于pthread_cond_wait在进一步理解!
        }

        _q.push(in);
        // if(_q.size() > _productor_water_line) pthread_cond_signal(&_c_cond);
        pthread_cond_signal(&_c_cond);
        // pthread_mutex_unlock(&_mutex);
    }
    void Pop(T *out)       // 消费者的
    {
        LockGuard lockguard(&_mutex);
        // pthread_mutex_lock(&_mutex);
        while(IsEmpty())
        {
            // 阻塞等待
            pthread_cond_wait(&_c_cond, &_mutex);
        }

        *out = _q.front();
        _q.pop();
        // if(_q.size() < _consumer_water_line) pthread_cond_signal(&_p_cond);
        pthread_cond_signal(&_p_cond);
        // pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
    }
private:
    std::queue<T> _q;
    int _capacity; // _q.size() == _capacity, 满了,不能在生产,_q.size() == 0, 空,不能消费了
    pthread_mutex_t _mutex;
    pthread_cond_t _p_cond; // 给生产者的
    pthread_cond_t _c_cond; // 给消费者的


};

                      感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       Linux下的多线程编程:原理、工具及应用(3),Linux练功 初阶功法,linux,运维,后端,服务器

                                                                        给个三连再走嘛~  文章来源地址https://www.toymoban.com/news/detail-840807.html

到了这里,关于Linux下的多线程编程:原理、工具及应用(3)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • Qt的多线程编程

    并发 当有多个线程在操作时,如果系统 只有一个CPU ,则它根本不可能真正同时进行一个以上的线程,它只能把 CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其他线程处于挂起状态。 虽然看起来所有 线程都是一起执行

    2024年02月08日
    浏览(45)
  • “深入理解Java的多线程编程“

    多线程编程是指在一个程序中同时运行多个线程,以提高程序的并发性和性能。Java是一门支持多线程编程的强大编程语言,提供了丰富的多线程相关类和接口。 在Java中,可以通过以下方式实现多线程编程: 继承Thread类:创建一个继承自Thread类的子类,并重写run()方法,在

    2024年02月13日
    浏览(67)
  • C# 中的多线程和异步编程

    最近在看代码的过程中,发现有很多地方涉及到多线程、异步编程,这是比较重要且常用的知识点,而本人在这方面还理解尚浅,因此开始全面学习C#中的多线程和异步编程,文中部分内容摘抄自一位前辈的网站:网址链接,为了更便于理解和学习,本人还在个别地方做了一

    2023年04月08日
    浏览(50)
  • Android中的多线程编程与异步处理

    在移动应用开发中,用户体验是至关重要的。一个流畅、高效的应用能够吸引用户并提升用户满意度。然而,移动应用面临着处理复杂业务逻辑、响应用户输入、处理网络请求等多个任务的挑战。为了确保应用的性能和用户体验,多线程编程和异步处理成为了不可或缺的技术

    2024年02月11日
    浏览(54)
  • Python小姿势 - Python的多线程编程

    Python的多线程编程 Python的多线程编程提供了一个非常简单的方法来让一个Python程序同时运行多个任务。这个方法通过创建新的线程来实现,线程可以被视为一个单独的执行流程。 为了创建一个新线程,我们需要使用Python的_thread模块中的start_new_thread()函数。它需要两个参数:

    2024年02月04日
    浏览(39)
  • 一文读懂flutter线程: 深入了解Flutter中的多线程编程

    在移动应用开发领域,Flutter已经成为了一个备受欢迎的框架,用于创建高性能、跨平台的应用程序。Flutter的一个关键特性是其能够轻松处理多线程编程,以改进应用程序的性能和响应性。本文将深入探讨Flutter中的多线程编程,包括为什么需要多线程、如何在Flutter中创建和管

    2024年01月20日
    浏览(83)
  • 如何在Python编程中应用Linux环境下的框架,以实现高效算法?

    python是一种广泛使用的编程语言,能够帮助开发人员快速开发高效的算法。与此同时,linux环境下提供了许多优秀的框架,可以进一步提高Python编程的效率。本文将介绍如何在Python编程中应用Linux环境下的框架,以实现高效算法。 一、Python和Linux环境的优势 Python是一种易学易

    2024年02月05日
    浏览(47)
  • 深入浅出Java的多线程编程——第二篇

    目录 前情回顾 1. 中断一个线程 1.1 中断的API 1.2 小结 2. 等待一个线程  2.1 等待的API 3. 线程的状态 3.1 贯彻线程的所有状态 3.2 线程状态和状态转移的意义 4. 多线程带来的的风险-线程安全 (重点) 4.1 观察线程不安全 4.2 线程安全的概念 4.3 线程不安全的原因 4.3.1 修改共享数据

    2024年02月07日
    浏览(82)
  • 编程小白的自学笔记八(python中的多线程)

     编程小白的自学笔记七(python中类的继承) 编程小白的自学笔记六(python中类的静态方法和动态方法)  编程小白的自学笔记五(Python类的方法)  编程小白的自学笔记四(正则表达式模块search函数)  编程小白的自学笔记三(Python正则表达式)  目录 系列文章目录 前言

    2024年02月16日
    浏览(50)
  • 目标检测YOLO实战应用案例100讲-面向恶劣环境下的多模态 行人识别

    目录 前言 国内外研究现状  可见光行人目标识别  红外行人目标识别 

    2024年02月07日
    浏览(42)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包