多线程c++

目录

1.join和detach区别

2.lock_guard和unique_lock 

3.原子操作

4.条件变量condition_variable 

5.future 和 promise 


1.join和detach区别

①不使用join和detach

#include <iostream>
#include <thread>
#include <windows.h>
 
using namespace std;
 
void t1()  //普通的函数,用来执行线程
{
    for (int i = 0; i < 10; ++i)
    {
        cout <<" t1 :"<< i <<"  ";
        _sleep(1);
    }
}
void t2()
{
    for (int i = 11; i < 20; ++i)
    {
        cout <<" t2 :"<< i <<"  ";
        _sleep(1);
    }
}
int main()
{
    thread th1(t1);  //实例化一个线程对象th1,使用函数t1构造,然后该线程就开始执行了(t1())
    thread th2(t2);

    cout << "here is main\n\n";
    system("pause");
    return 0;
}

每次输出的结果都不一样

②使用join

#include <iostream>
#include <thread>
#include <windows.h>
 
using namespace std;
 
void t1()  //普通的函数,用来执行线程
{
    for (int i = 0; i < 10; ++i)
    {
        cout <<" t1 :"<< i <<"  ";
        _sleep(1);
    }
}
void t2()
{
    for (int i = 11; i < 20; ++i)
    {
        cout <<" t2 :"<< i <<"  ";
        _sleep(1);
    }
}
int main()
{
    thread th1(t1);  //实例化一个线程对象th1,使用函数t1构造,然后该线程就开始执行了(t1())
    thread th2(t2);
 
    th1.join(); // 必须将线程join或者detach 等待子线程结束主进程才可以退出
    th2.join(); 
 
    cout << "here is main\n\n";
    system("pause");
    return 0;
}

join会将主线程和子线程th1 th2分离,子线程执行完才会执行主线程。(两个子线程输出每次仍不一样)

③使用detach

#include <iostream>
#include <thread>

#include <windows.h>
 
using namespace std;
 
void t1()  //普通的函数,用来执行线程
{
    for (int i = 0; i < 10; ++i)
    {
        cout <<" t1 :"<< i <<"  ";
        _sleep(1);
    }
   
}
void t2()
{
    for (int i = 11; i < 20; ++i)
    {
        cout <<" t2 :"<< i <<"  ";
        _sleep(1);
    }
}
int main()
{
    thread th1(t1);  //实例化一个线程对象th1,使用函数t1构造,然后该线程就开始执行了(t1())
    thread th2(t2);
 
    th1.detach();
    th2.detach();
 
    cout << "here is main\n\n";
    system("pause");
    return 0;
}

detach也会将主线程和子线程th1 th2分离,但是主线程执行过程子线程仍会执行

总结:

1. join会阻塞当前的线程,直到运行的线程结束。(例如上面第二段代码主线程被阻塞,直到子线程执行完才会执行join之后的主线程代码)

2.detach从线程对象中分离出执行线程,允许线程独立的执行。(上面第三段代码,主线程和两个子线程独立的执行)

参考文章

2.lock_guard和unique_lock 

线程的使用一定需要搭配锁mutex,它是用来保证线程同步的,防止不同的线程同时操作同一个共享数据。

①使用mutex,使用lock函数上锁,unlock解锁

#include <iostream>
#include <thread>
#include <mutex>
#include <stdlib.h>
 
int cnt = 20;
std::mutex m;
void t1()
{
    while (cnt > 0)
    {    
        m.lock();
        if (cnt > 0)
        {
            --cnt;
            std::cout << cnt << std::endl;
        }
        m.unlock();
    }
}
void t2()
{
    while (cnt < 20)
    {
        m.lock();
        if (cnt < 20)
        {
            ++cnt;
            std::cout << cnt << std::endl;
        }
        m.unlock();
    }
}
 
int main(void)
{
    std::thread th1(t1);
    std::thread th2(t2);
 
    th1.join();    //等待t1退出
    th2.join();    //等待t2退出
 
    std::cout << "here is the main()" << std::endl;
    system("pause");
    return 0;
}

②使用lock_guard

使用mutex比较繁琐,需要上锁和解锁,c++提供了lock_guard,它是基于作用域的,能够自解锁,当该对象创建时,它会像m.lock()一样获得互斥锁,当生命周期结束时,它会自动析构(unlock),不会因为某个线程异常退出而影响其他线程。

#include <iostream>
#include <thread>
#include <mutex>
#include <stdlib.h>
 
int cnt = 20;
std::mutex m;
void t1()
{
    while (cnt > 0)
    {    
        std::lock_guard<std::mutex> lockGuard(m);
        // m.lock();
        if (cnt > 0)
        {
            --cnt;
            std::cout << cnt << std::endl;
        }
        // m.unlock();
    }
}
void t2()
{
    while (cnt < 20)
    {
        std::lock_guard<std::mutex> lockGuard(m);
        // m.lock();
        if (cnt < 20)
        {
            ++cnt;
            std::cout << cnt << std::endl;
        }
        // m.unlock();
    }
}
 
int main(void)
{
    std::thread th1(t1);
    std::thread th2(t2);
 
    th1.join();    //等待t1退出
    th2.join();    //等待t2退出
 
    std::cout << "here is the main()" << std::endl;
    system("pause");
    return 0;
}

③使用unique_lock

lock_guard有个很大的缺陷,在定义lock_guard的地方会调用构造函数加锁,在离开定义域的话lock_guard就会被销毁,调用析构函数解锁。这就产生了一个问题,如果这个定义域范围很大的话,那么锁的粒度就很大,很大程序上会影响效率。

因此提出了unique_lock,这个会在构造函数加锁,然后可以利用unique.unlock()来解锁,所以当锁的颗粒度太多的时候,可以利用这个来解锁,而析构的时候会判断当前锁的状态来决定是否解锁,如果当前状态已经是解锁状态了,那么就不会再次解锁,而如果当前状态是加锁状态,就会自动调用unique.unlock()来解锁。而lock_guard在析构的时候一定会解锁,也没有中途解锁的功能。

参考文章

3.原子操作

原子性操作库(atomic)是C++11中新增的标准库,它提供了一种线程安全的方式来访问和修改共享变量,避免了数据竞争的问题。在多线程程序中,如果多个线程同时对同一个变量进行读写操作,就可能会导致数据不一致的问题。原子性操作库通过使用原子操作来保证多个线程同时访问同一个变量时的正确性。

例,在多线程中进行加一减一操作,循环一定次数

#include <iostream>
#include <thread>
#include <atomic>
#include <time.h>
#include <mutex>
using namespace std;
 
#define MAX 100000
#define THREAD_COUNT 20
 
int total = 0;
mutex mt;
 
void thread_task()
{
    for (int i = 0; i < MAX; i++)
    {
        mt.lock();
        total += 1;
        total -= 1;
        mt.unlock();
    }
}
 
int main()
{
    clock_t start = clock();
    thread t[THREAD_COUNT];
    for (int i = 0; i < THREAD_COUNT; ++i)
    {
        t[i] = thread(thread_task);
    }
    for (int i = 0; i < THREAD_COUNT; ++i)
    {
        t[i].join();
    }
    
    clock_t finish = clock();
    // 输出结果
    cout << "result:" << total << endl;
    cout << "duration:" << finish - start << "ms" << endl;
 
    system("pause");
    return 0;
}

从结果来看非常耗时,使用原子atomic,不需要使用mutex,(注意添加头文件)

#include <iostream>
#include <thread>
#include <atomic>
#include <time.h>
#include <mutex>
using namespace std;
 
#define MAX 100000
#define THREAD_COUNT 20
 
//原子操作  也不需要使用互斥锁
// atomic_int total(0);
atomic<int> total;

void thread_task()
{
    for (int i = 0; i < MAX; i++)
    {
        total += 1;
        total -= 1;
    }
}
 
int main()
{
    clock_t start = clock();
    thread t[THREAD_COUNT];
    for (int i = 0; i < THREAD_COUNT; ++i)
    {
        t[i] = thread(thread_task);
    }
    for (int i = 0; i < THREAD_COUNT; ++i)
    {
        t[i].join();
    }
    
    clock_t finish = clock();
    // 输出结果
    cout << "result:" << total << endl;
    cout << "duration:" << finish - start << "ms" << endl;
    
    system("pause");
    return 0;
}

 关于具体函数的使用参考官方文档

4.条件变量condition_variable 

条件变量是c++11引入的一种同步机制,它可以阻塞一个线程或者多个线程,直到有线程通知或者超时才会唤醒正在阻塞的线程, 条件变量需要和锁配合使用,这里的锁就是上面的unique_lock。

其中有两个非常重要的接口,wait()和notify_one(),wait()可以让线程陷入休眠状态,notify_one()就是唤醒真正休眠状态的线程。还有notify_all()这个接口,就是唤醒所有正在等待的线程。

#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
 
deque<int> q;
mutex mt;
condition_variable cond;
 
void thread_producer()
{
    int count = 10;
    while (count > 0)
    {
        unique_lock<mutex> unique(mt);
        q.push_front(count);
        unique.unlock();    // 解锁才能去唤醒
        cout << "producer a value: " << count << endl;
        cond.notify_one();   // 唤醒wait的线程会阻塞当前线程,
        this_thread::sleep_for(chrono::seconds(1));
        count--;
    }
}
 
void thread_consumer()
{
    int data = 0;
    while (data != 1)
    {
        unique_lock<mutex> unique(mt);
        while (q.empty())
            cond.wait(unique);      //解锁,线程被阻塞处于等待状态 |||| 被唤醒后优先获得互斥锁

        data = q.back();   // 使用 back 函数获取最后一个元素
        q.pop_back();
        cout << "consumer a value: " << data << endl;

        // 下面这行可以不写,unique_lock 离开作用域会调用析构判断是否解锁。
        // unique.unlock();     // 解锁后thread_producer获得互斥锁
    }
}
 
int main()
{
    thread t1(thread_consumer);
    thread t2(thread_producer);
    t1.join();
    t2.join();

    system("pause");
    return 0;
}

thread_consumer 的执行流程如下

1.unique_lock<mutex> unique(mt); - 上锁互斥锁。

2.cond.wait(unique); - 释放互斥锁并等待通知。

3.当被通知后,重新获得互斥锁。

4.继续执行后面的代码,包括 data = q.back(); 和 q.pop_back();。

在上述过程中,unique_lock 会自动处理锁的上锁和解锁操作。当 cond.wait(unique); 执行时,它会将互斥锁解锁,并将线程置于等待状态。当线程被唤醒后,unique_lock 会自动重新对互斥锁上锁。

在 thread_producer 中,它获取互斥锁的步骤如下

1.unique_lock<mutex> unique(mt); - 上锁互斥锁。

2.q.push_front(count); - 操作共享资源。

3.unique.unlock(); - 解锁互斥锁。

4.cond.notify_one(); - 发送通知。

在这个过程中,unique_lock 会在 unique.unlock(); 处解锁互斥锁,以允许其他线程进入相应的临界区。当 cond.notify_one(); 发送通知后,thread_consumer 会被唤醒,并在 unique_lock 重新获取互斥锁后继续执行。

这两个互斥锁操作是同步的,不会引起冲突,因为它们是针对不同的互斥锁对象进行的。thread_consumer 上的互斥锁 mt 与 thread_producer 上的互斥锁 mt 是两个不同的互斥锁对象。

注意:在使用条件变量时,一般要在循环中等待条件,因为线程被唤醒后需要重新检查条件是否真的满足。 

更详细的参考文档

5.future 和 promise 

TODO

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/359247.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux文本三剑客-grep

1.grep简介&#xff1a; grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具&#xff0c;它能使用正则表达式搜索文本&#xff0c;并把匹配的行打印出来&#xff0c;都是按行处理的。 grep 最主要…

腾讯云云监控实践:使用云审计 CloudAudit SDK 精准管理腾讯云资源

文章目录 一、什么是腾讯云的操作审计 CloudAudit二、CloudAudit 有哪些优势三、CloudAudit 应用场景举例3.1 安全分析3.2 资源变更跟踪3.3 合规性审计 四、使用云审计 SDK 进行云监控4.1 安装环境包 PHP4.2 下载并解压云审计 PHP SDK4.3 创建的腾讯云持久证书&#xff08;如果…

解决:ModuleNotFoundError: No module named ‘selenium’

解决&#xff1a;ModuleNotFoundError: No module named ‘selenium’ 文章目录 解决&#xff1a;ModuleNotFoundError: No module named selenium背景报错问题报错翻译报错位置代码报错原因解决方法方法一&#xff0c;直接安装方法二&#xff0c;手动下载安装方法三&#xff0…

【Tomcat与网络2】一文理解Servlet是怎么工作的

在前面&#xff0c;我们研究了如何用idea来启动一个Servlet程序&#xff0c;今天我们就再来看一下Servlet是如何工作的。 目录 1.Servlet 介绍 2.Servlet 容器工作过程 3.Servlet的扩展 不管是电脑还是手机浏览器&#xff0c;发给服务端的就是一个 HTTP 格式的请求&#xf…

摄像头提示sd卡未格式化怎么回事?怎么解决

作为摄像头用户&#xff0c;往往会遇到或多或少的技术问题。而当摄像头显示"SD卡未格式化"的提示时&#xff0c;这可能令一些用户感到困惑和担忧。在本文中&#xff0c;我们将解释这个提示的原因&#xff0c;并提供一些建议来解决这一问题。我们相信本文会让您更加了…

select的change方法如何传递多个参数

element-ui中select的change方法传递多个参数 element-ui中的select&#xff0c;checkbox等组件的change方法的回调函数只有当前选择的val&#xff0c;如果想再传入自定义参数怎么办&#xff1f; 不能够传入自定义的参数&#xff0c;在进行某些操作时&#xff0c;会比较困难&…

[设计模式Java实现附plantuml源码~结构型]对象的间接访问——代理模式

前言&#xff1a; 为什么之前写过Golang 版的设计模式&#xff0c;还在重新写Java 版&#xff1f; 答&#xff1a;因为对于我而言&#xff0c;当然也希望对正在学习的大伙有帮助。Java作为一门纯面向对象的语言&#xff0c;更适合用于学习设计模式。 为什么类图要附上uml 因为很…

Ubuntu18.04安装Matlab流程笔记

提示:博主取舍了很多大佬的博文并亲测有效,分享笔记邀大家共同学习讨论 Ubuntu18.04 安装Matlab流程 下载安装包和破解文件安装Matlab注册并运行 下载安装包和破解文件 matlabR2019A源码 提取码:2ztb 下载的Linux matlab2018a文件夹内有三个文件&#xff1a; # 解压Matlab201…

深度学习之循环神经网络 (基础)

循环神经网络简称为RNN&#xff0c;&#xff08;之前讲到的卷积神经网络简称为CNN&#xff09;。 以前我们在使用全链接网络的时候&#xff0c;我们将这种网络叫做Dense 或者是Deep。 Dense链接指的是全链接的。 我们输入的数据是数据样本的不同特征&#xff1a;x1&#xff…

VScode设置行宽提示线

vscode 设置行宽提示线&#xff0c;可以按如下步骤设置&#xff1a; 打开设置 搜索框输入 rulers&#xff0c;选择用户&#xff0c;点击 在 settings.json 中编辑 跳转到一个 json 文件后&#xff0c;将字段 rulers 对应值设置为 80 补充&#xff1a;如果您在您的 json 配…

JUC并发编程-四大函数式接口、Stream 流式计算、ForkJoin并行执行任务

12. 四大函数式接口 新时代的程序员&#xff1a;lambda表达式、链式编程、函数式接口、Stream流式计算 函数式接口&#xff1a;只有一个方法的接口&#xff0c;可以有一些默认的方法 如&#xff1a;Runnable接口函数 1&#xff09;Function 函数型接口 public class Functio…

java 图书管理系统 spring boot项目

java 图书管理系统ssm框架 spring boot项目 功能有管理员模块&#xff1a;图书管理&#xff0c;读者管理&#xff0c;借阅管理&#xff0c;登录&#xff0c;修改密码 读者端&#xff1a;可查看图书信息&#xff0c;借阅记录&#xff0c;登录&#xff0c;修改密码 技术&#…

常用芯片学习——CD4094芯片

CD4094 8位移位寄存器/3态输出缓冲器 使用说明 CD4094是由一个 8 位串行移位寄存器和一个 3 态输出缓冲器组成的 CMOS 集成电路。寄存器带有存储锁存功能&#xff0c;集成电路根据 STROBE 信号确定锁存器是否接收移位寄存器各位数据&#xff0c;数据是否由锁存器传输到 3 态输…

在Windows上安装与配置Apache服务并结合内网穿透工具实现公网远程访问本地内网服务

文章目录 前言1.Apache服务安装配置1.1 进入官网下载安装包1.2 Apache服务配置 2.安装cpolar内网穿透2.1 注册cpolar账号2.2 下载cpolar客户端 3. 获取远程桌面公网地址3.1 登录cpolar web ui管理界面3.2 创建公网地址 4. 固定公网地址 前言 Apache作为全球使用较高的Web服务器…

如何通过Hive/tez与Hadoop的整合快速实现大数据开发

一、Hive的功能 Hive是基于Hadoop的一个外围数据仓库分析组件&#xff0c;可以把Hive理解为一个数据仓库&#xff0c;但这和传统的数据库是有差别的。 传统数据库是面向业务存储&#xff0c;比如 OA、ERP 等系统使用的数据库&#xff0c;而数据仓库是为分析数据而设计的。同时…

移动Web-动画

1、动画-animation 过渡&#xff1a;实现两个状态间的变化过程 动画&#xff1a;实现多个状态间的变化过程&#xff0c;动画过程可控&#xff08;重复播放、最终画面、是否暂停&#xff09; 1.1 实现步骤 1.1.1 定义动画 1.1.2 使用动画 <!DOCTYPE html> <html lang…

备战蓝桥杯---数据结构与STL应用(入门2)

话不多说&#xff0c;直接看题&#xff1a; 前4个操作我们可以用deque解决&#xff0c;第5个我们不用真的去反转&#xff0c;调换一下头尾即可。 下面是AC代码&#xff1a; #include<bits/stdc.h> using namespace std; int n,m,k,k1,ee0; bool cm1(int a,int b){retur…

C++ 数论相关题目 博弈论:拆分-Nim游戏

给定 n 堆石子&#xff0c;两位玩家轮流操作&#xff0c;每次操作可以取走其中的一堆石子&#xff0c;然后放入两堆规模更小的石子&#xff08;新堆规模可以为 0 &#xff0c;且两个新堆的石子总数可以大于取走的那堆石子数&#xff09;&#xff0c;最后无法进行操作的人视为失…

网络安全从入门到精通(超详细)学习路线

首先看一下学网络安全有什么好处&#xff1a; 1、可以学习计算机方面的知识 在正式学习网络安全之前是一定要学习计算机基础知识的。只要把网络安全认真的学透了&#xff0c;那么计算机基础知识是没有任何问题的&#xff0c;操作系统、网络架构、网站容器、数据库、前端后端等…

GitCode|部分项目开源代码

1.EasyKeyboard 基于MFC的简单软键盘&#xff0c;使用vs2017开发 PangCoder / EasyKeyboard GitCode基于Windows平台的软键盘&#xff0c;使用VS2017开发&#xff0c;使用MFC框架https://gitcode.net/qq_36251561/easykeyboard 2.EncoderSimulator 基于WPF应用的编码器模拟工…