基础篇:多线程所需知识:

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

前言:

  • 这里的多线程主要指算法部署时所涉及的多线程内容,对于其他多线程知识需要自行补充
  • 常用组件有thread、mutex、promise、future、condition_variable
  • 启动线程,thread,以及join、joinable、detach、类函数启动为线程
  • 生产者消费者模式
  • 队列溢出的问题,生产太快,消费太慢。如何实现溢出限制
  • 生产者如何拿到消费反馈
  • RAII+接口模式的生产者消费者封装,以及多batch的体现

1、线程:

本单元是https://blog.csdn.net/zhuangtu1999/article/details/130903594?spm=1001.2014.3001.5501#t1的实例化单元,更加注重在实战中如何运用线程知识

基础篇:多线程所需知识:,多线程基础,c++

         上图是我们之前的一个例子,可以看到如果没有t.join(),那么线程是会崩掉的,因为他的生命周期在main()函数里面,所以当main结束之后他也会析构掉,但线程里面还在执行,所以会出现错误。

基础篇:多线程所需知识:,多线程基础,c++

         那如果要是没有启动线程就join呢?

        可以看到也是会出错的。

所以可以发现:当线程启动了,就必须join,但如果线程没有启动,那就一定不能join,那这样肯定是很麻烦的,所以引申出来了下面的joinable概念:

joinable:

 if (t1.joinable())
    {
       t1.join();
    }

这样的话无论线程有没有启动,退出的时候都可以正常退出。

detach

 detach是分离线程,取消管理权,使线程变成野线程。但这个通常不建议使用

如果一个线程一旦detach之后,线程就交给了系统作管理,当程序结束后自动退出

基础篇:多线程所需知识:,多线程基础,c++

 可以看到在延迟了500毫秒之后,worker down这条语句来不及打印,就会直接退出。

参数传递:

基础篇:多线程所需知识:,多线程基础,c++

 在传递引用类型时,传入参数应用ref

 类函数:

如果我们正常想搞一个类函数私有线程和方法,那我们首先想到的是可以这样做:

class Infer{
    public:

        Infer(){

            worker_thread = thread(Infer_worker);
    
    }
    private:
    
    thread  worker_thread;
    
    void Infer_worker(){

    }
};

但这样做会报错:

基础篇:多线程所需知识:,多线程基础,c++

 显示他不是一个静态函数,但如果我们想要把这个函数加上static

基础篇:多线程所需知识:,多线程基础,c++

这样做的确可以成功运行,但每次都要加上sellf很麻烦。

基础篇:多线程所需知识:,多线程基础,c++

 可以通过取地址方式,保留this参数,这样做可以避免static无法使用this->的操作。

2、生产者消费者模式

生产者:

queue<string> qjobs_;

void video_capture(){
    int pic_id = 0 ;
    while (true)
    {
        /* code */
        char name[100];
        sprintf(name ,"PIC - %d" , pic_id++);
        printf("生产了一个新图片:%s\n" , name );
        qjobs_.push(name);
        this_thread::sleep_for(chrono::milliseconds(1000));
    
    }
      
}

消费者:

void infer_worker(){

    while (true)
    {
        if (!qjobs_.empty())
        {
            /* code */auto pic = qjobs_.front();
            qjobs_.pop();

            printf("消费掉一个图片:%s \n" , pic.c_str());
            this_thread::sleep_for(chrono::milliseconds(1000));
        }
        this_thread::yield();
        
    }
    
}

结果就是这样:基础篇:多线程所需知识:,多线程基础,c++

由于生产一个图片需要1ms,消费一张图片需要1ms,所以二者刚好是收支平衡的状态。

但这个设计到了一个共享资源访问的问题,queue不是线程安全的。

这就是设计到了mutex(https://blog.csdn.net/zhuangtu1999/article/details/130917521?spm=1001.2014.3001.5501)

我们设计了一个mutex然后在创建和消费的时候都加上🔓:

mutex lock_;
void video_capture(){
    int pic_id = 0 ;
    while (true)
    {
        /* code */
       { 
        lock_guard<mutex>  l(lock_);
            char name[100];
            sprintf(name ,"PIC - %d" , pic_id++);
            printf("生产了一个新图片:%s\n" , name );
            qjobs_.push(name);
            this_thread::sleep_for(chrono::milliseconds(2000));
            
        }
    
    }
      
}

void infer_worker(){

    while (true)
    {
        if (!qjobs_.empty())
        {
                {           
                    lock_guard<mutex> l(lock_);
                    auto pic = qjobs_.front();
                    qjobs_.pop();

                    printf("消费掉一个图片:%s \n" , pic.c_str());
                }
            this_thread::sleep_for(chrono::milliseconds(1000));
        }
        this_thread::yield();
        
    }
    
}

这就变成了一个原子操作。

condtion_varible:

如果生产太快,消费太慢,队列就会不断的增长,如果存储的是图片,可能就会造成显存爆炸:

基础篇:多线程所需知识:,多线程基础,c++

 所以设置一个上限就很有必要,这时就需要<codition_variable>这个头文件顾名思义,这个意思是条件变量。

            if (qjobs_.size() > limite)
            {
                /* code */wait();
            }
            
            qjobs_.push(name);            
            //如果队列满了,就先不生产,等有空间了再生产

如果队列满了,就先不生产,等有空间了再生产,那就在push前面做一个判断。

如何通知函数,让wait退出,条件:消费掉一个就可以去通知

condition_variable cv_;
void video_capture(){
    int pic_id = 0 ;
    while (true)
    {
        /* code */
       { 
        unique_lock<mutex>  l(lock_);
            char name[100];
            sprintf(name ,"PIC - %d" , pic_id++);
            printf("生产了一个新图片:%s , qjob_.size = %d \n" , name , qjobs_.size() );
            if (qjobs_.size() > limit)
            {
                // /* code */wait();
                            //如果队列满了,就先不生产,等有空间了再生产
                            //如何通知函数,让wait退出,条件:消费掉一个就可以去通知
                            cv_.wait(l, [& ](   ){
                                return qjobs_.size() < limit;
                                //return false 继续等待
                                //return true 跳出等待
                            });
            }
            
            qjobs_.push(name);            
            //如果队列满了,就先不生产,等有空间了再生产
        }
                this_thread::sleep_for(chrono::milliseconds(500));
    }
      
}

要注意:这里把lock_guard 换成了 unique_lock 这是为了能提前解锁。

而我们之前所说的队列满了就不生产实现了,还有通知没有实现。

void infer_worker(){

    while (true)
    {
        if (!qjobs_.empty())
        {
                {           
                    lock_guard<mutex> l(lock_);
                    auto pic = qjobs_.front();
                    qjobs_.pop();

                    printf("消费掉一个图片:%s \n" , pic.c_str());
                    cv_.notify_one();
                }
            this_thread::sleep_for(chrono::milliseconds(1000));
        }
        this_thread::yield();
        
    }
    
}

就是在消费掉过后使用notify_one函数

wait的流程:

一旦进入wait()则解锁,解锁时video的线程停止运行,而infer的线程则获得了锁,正常进行

一旦退出wait()则加锁,加锁后video和infer又恢复为原子操作。

 notify_one() 是 condition_variable 类的一个成员函数,它的作用是唤醒一个正在等待 condition_variable 的线程,使其获得锁并继续执行。它只会唤醒一个等待的线程,所以如果有多个线程在等待,那么只有一个线程会被唤醒,其他线程还是处于等待状态。使用 notify_one() 的方法是:在线程中使用 std::unique_lock 对象来锁定 condition_variable,然后调用 condition_variable 的 wait() 方法来等待,当线程需要唤醒其他线程时,调用 notify_one() 来唤醒等待的线程。
 

notify_one:此时调用notify_one会随机唤醒一个阻塞的线程,而其余的线程将仍然处于阻塞状态,等待下一次唤醒。


notify_all:调用notify_all则会唤醒所有线程,线程会争抢锁,当然只有一个线程会获得到锁,而其余未获得锁的线程也将不再阻塞,而是进入到类似轮询的状态,等待锁资源释放后再去争抢。

假如同时有10个线程阻塞在wait方法上,则需要调用10次notify_one,而仅仅只需要调用1次notify_all
 

生产者如何拿到消费反馈:

也就是在我拿到了infer后的结果,我想将其送回到生产者该怎么办?

普通模式:

detection -> infer

face -> infer

feather -> push

异步模式:

detection -> push

face -> push

feather -> push

对于普通模式而言,每个线程的上下文都要去初始化,而且调用什么的也很费时间 , 所以使用异步操作才是更好的高性能选择。

这里需要用到future库

struct Job{
    shared_ptr<promise<string>> pro ;
    string input;
};

因为本次操作的是promise类型的,所以需要重新定义一个结构体

void video_capture(){
    int pic_id = 0 ;
    while (true)
    {
         Job jobs;
        /* code */
       { 
        unique_lock<mutex>  l(lock_);
            char name[100];
            sprintf(name ,"PIC - %d" , pic_id++);
            printf("生产了一个新图片:%s , qjob_.size = %d \n" , name , qjobs_.size() );
            // if (qjobs_.size() > limit)
            // {
                // /* code */wait();
                            //如果队列满了,就先产,等有空间了再生产
                            //如何通知函数,让wait退出,条件:消费掉一个就可以去通知
                            cv_.wait(l, [& ](   ){
                                return qjobs_.size() < limit;
                                //return false 继续等待
                                //return true 跳出等待
                            });
            // }
            jobs.pro.reset(new promise<string>());//promise就是说未来必须要返回一个结果
            jobs.input = name;
            qjobs_.push(jobs);            
            //如果队列满了,就先不生产,等有空间了再生产
        }
            auto result = jobs.pro->get_future().get();
            printf("%s-------->result:%s  \n" ,jobs.input.c_str(), result .c_str());
                this_thread::sleep_for(chrono::milliseconds(400));
    }
      
}

因为是jobs.pro.reset(new promise<string>())的变量,所以代表着一定会返还一个结果。


void infer_worker(){

    while (true)
    {
        if (!qjobs_.empty())
        {
                {           
                unique_lock<mutex> l(lock_);
                auto pjobs_ = qjobs_.front();
                qjobs_.pop();
                cv_.notify_one();
                printf("消费掉一个图片:%s \n" , pjobs_.input.c_str());

                    auto new_pic = pjobs_.input+"------infer";
                    pjobs_.pro->set_value(new_pic);
      
                }
            this_thread::sleep_for(chrono::milliseconds(1000));
        }
        this_thread::yield();
        
    }
    
}

所以在infer里加上了

                    auto new_pic = pjobs_.input+"------infer";
                    pjobs_.pro->set_value(new_pic);

最后在生产者中

 auto result = jobs.pro->get_future().get();
            printf("%s-------->result:%s  \n" ,jobs.input.c_str(), result .c_str());

但这部分要放在锁外,要不然会因为一直等待infer传回结果,而此时infer所在线程不被执行。造成死锁

 文章来源地址https://www.toymoban.com/news/detail-613247.html

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

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

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

相关文章

  • 多线程基础知识点梳理

    进程(process):进程是计算机中的一个任务,比如打开浏览器、IntelliJ IDEA。 线程(thread):线程是进程内部的子任务。比如IDEA在敲代码的同时还能自动保存、自动导包,都是子线程做的。 进程和线程的关系就是一个进程包含一个或多个线程。 线程是操作系统调度的最小任

    2024年02月04日
    浏览(52)
  • java基础之线程知识点

    操作系统(OS)中并发(同时)执行的多个程序任务 宏观并行,微观串行 在一个时间段内,CPU会将时间段划分为若干个时间片,一个时间片是能被一个程序拥有,且只有拥有时间片的程序才能执行自身内容,所以当时间片的划分足够细小,交替频率足够快,就会形成并行的假象,时间上仍然是

    2024年02月01日
    浏览(47)
  • java并发编程:多线程基础知识介绍

    最初的计算机只能接受一些特定的指令,用户每输入一个指令,计算机就做出一个操作。当用户在思考或者输入时,计算机就在等待。这样效率非常低下,在很多时候,计算机都处在等待状态。 后来有了 批处理操作系统 ,把一系列需要操作的指令写下来,形成一个清单,一次

    2024年02月07日
    浏览(49)
  • 【Java之家-编程的衣柜】线程的基础知识及线程与进程的联系

    线程是什么 一个线程就是一个 “执行流”. 每个线程之间都可以按照顺讯执行自己的代码. 多个线程之间 “同时” 执行 着多份代码. 轻量级进程 - 线程(Thread) 为什么要有线程 首先,“并发编程”成为“刚需” 其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量

    2024年02月07日
    浏览(52)
  • 【Java】遨游在多线程的知识体系中(基础篇一)

    因为知识比较多,想把文章字数控制在一定范围之内,本文只有先发一篇多线程部分篇幅,之后的知识也会马上赶出来的,有什么有问题的地方经管提出,会虚心接受,并且认真改正。 创建一个子类继承自Thread,重写Thread中的run方法, 这个方法内部就包含了这个线程要执行

    2024年02月01日
    浏览(32)
  • ctf比赛涉及的方面以及所需知识

    Reverse题目涉及到软件逆向、破解技术等,要求有较强的反汇编、反编译功底。主要考查参赛选手的逆向分析能力。所需知识:汇编语言、加密与解密、常见反编译工具 PwnPwn 在黑客俚语中代表着攻破,获取权限,在 CTF 比赛中它代表着溢出类的题目,其中常见类型溢出漏洞有

    2024年02月11日
    浏览(40)
  • 技术写作者所需的关键技能和知识

    成为一名优秀的技术写作者需要以下核心技能: 写作技巧: 优秀的语言和语法掌握能力 出色的拼写能力 对标点符号的理解 技术知识: 对复杂技术概念有很好的理解 将复杂概念转换为易于理解的内容 研究技能: 识别最终用户的需求 快速理解新概念和技术的能力 工具技能: 熟练

    2024年02月04日
    浏览(46)
  • c#多线程—基础概念到“双色球”项目实现(附知识点目录、代码、视频)

    总结:视频中对于多线程讲的非常透彻,从线程基础概念—.net不同版本出现的线程方法—多线程常出现问题—双色球项目实践,每个知识点都有代码实操,受益匪浅。附上学习笔记和实操代码。 视频 线程:程序执行的最小单位,任何操作都是由线程完成的,使用同步时,资

    2024年02月11日
    浏览(58)
  • 大数据相关职位的知识储备与系统学习路线规划以及所需时间

    想要成为一名数据分析师,需要具备以下几个方面的知识储备: 1、数据库知识:掌握 SQL 语言,了解数据表的设计、数据的存储与查询等基本概念 2、统计学知识:包括概率论、统计学、假设检验、方差分析等内容,能够熟练使用统计分析工具进行数据分析。 3、数据挖掘知

    2023年04月10日
    浏览(32)
  • MySQL面试题全解析:准备面试所需的关键知识点和实战经验

    MySQL支持多种数据存储引擎,其中最常见的是MyISAM和InnoDB引擎。可以通过使用\\\"show engines\\\"命令查看MySQL支持的存储引擎。 存储方式:MyISAM引擎将数据和索引分别存储在两个不同的文件中,一个是.MYD文件用于存储数据,一个是.MYI文件用于存储索引。而InnoDB引擎将数据和索引存储

    2024年02月12日
    浏览(33)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包