C++11的 thread多线程

2023-10-30

一、C++11的多线程类thread

C++11之前,C++库中没有提供和线程相关的类或者接口,因此在编写多线程程序时,Windows上需要调用CreateThread创建线程,Linux下需要调用clone或者pthread_create来创建线程,但是这样是直接调用了系统相关的API函数,编写的代码,无法做到跨平台编译运行。

C++11之后提供了thread线程类,可以很方便的编写多线程的程序。

std::thread定义了四种构造函数:

thread() noexcept;

template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );

thread( thread&& other ) noexcept;

thread( const thread& ) = delete;
  • 默认构造函数,创建一个空的 std::thread 执行对象。
  • 初始化构造函数,创建一个 std::thread 对象,可以传入任意函数对象,以及函数参数。在创建thread对象时,std::thread构建函数中的所有参数均会按值并以副本的形式保存成一个tuple对象。
  • 移动构造函数,调用成功之后该线程对象会转移。
  • 拷贝构造函数(被禁用),意味着线程无法拷贝。

随便提一下,当你创建了一个带参(非空的)线程对象时,该线程就会立即执行,不需要显式的调用start或者run 

举个例子,下面展示了四个构造函数的使用

#include <iostream>
#include <utility>
#include <thread>
#include <chrono>
using namespace std;

void f1(int n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 1 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
};

void f2(int& n)
{
    for (int i = 0; i < 5; ++i) {
        std::cout << "Thread 2 executing\n";
        ++n;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
};

class foo
{
public:
    void bar()
    {
        for (int i = 0; i < 5; ++i) {
            std::cout << "Thread 3 executing\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};

class baz
{
public:
    void operator()()
    {
        for (int i = 0; i < 5; ++i) {
            std::cout << "Thread 4 executing\n";
            ++n;
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
        }
    }
    int n = 0;
};

int main()
{
    int n = 0;
    foo f;
    baz b;
    std::thread t1([] {
        cout << "Hello World from lambda thread." << endl;
    });                  // 线程会立即执行

    std::thread t2(f1, n + 1); // 值传递
    std::thread t3(f2, std::ref(n)); //引用传递
    std::thread t4(std::move(t3)); // 移动构造函数,t4 is now running f2(). t3 is no longer a thread
    std::thread t5(&foo::bar, &f); // 在类的成员函数,t5 runs foo::bar() on object f
    std::thread t6(b); // 使用仿函数,t6 runs baz::operator() on a copy of object b
    //std::thread t6(std::ref(b));

    // 等待线程t2和t4,t5,t6执行完,main线程再继续运行
    t2.join();
    t4.join();
    t5.join();
    t6.join();
    std::cout << "Final value of n is " << n << '\n';
    std::cout << "Final value of f.n (foo::n) is " << f.n << '\n';
    std::cout << "Final value of b.n (baz::n) is " << b.n << '\n'; // 因为是复制对象,原对象的n不改变

	return 0;
}

  注解:

 join:等待子线程,调用线程main处于阻塞模式。直到由 子线程线程执行完毕, join 才返回,join()执行完成之后,底层线程id被设置为0,即joinable()变为false。

joinable:检查线程是否可被 join。检查当前的线程对象是否表示了一个活动的执行线程,由默认构造函数创建的线程是不能被 join 的。

detach:  将当前线程对象所代表的执行实例与该线程对象分离,不会阻塞当前main线程,使得线程的执行可以单独进行。一旦线程执行完毕,它所分配的资源将会被释放。

 void foo(int n)
 {
	 std::cout << "start run  " << endl;
	 std::cout << "run  " << n << endl;
 }

 int main()
 {
	 std::thread t1(foo, 1);
	 if (t1.joinable())
	 {
		 std::cout << "thread joinable" << std::endl;
	 }

	 std::cout <<"this_thread调用get_id  "<< std::this_thread::get_id() << std::endl;
	 std::cout << "t1调用get_id  " << t1.get_id() << std::endl;
	 t1.detach();

	 // detach之后,joinable返回false,get_id返回0x0
	 if (!t1.joinable())
	 {
		 std::cout << "t1 can not joinable" << std::endl;
	 }

	 std::cout << "t1调用get_id " <<t1.get_id() << std::endl;
     return 0;
}

运行结果

二、线程互斥

在多线程环境中运行的代码段,需要考虑是否存在竞态条件。如果存在竞态条件,我们就说该代码段不是线程安全的,是不能直接运行在多线程环境当中。对于这样的代码段,我们经常称之为临界区。临界区又称关键代码段,指的是一小段代码在代码执行前,需要独占一些资源资源,比如访问CPU,打印机等。对于临界区资源,多线程环境下需要保证它以原子操作执行,要保证临界区的原子操作,就需要用到线程间的互斥操作-锁机制。

int g_i = 0;
mutex g_mutex;
void c()
{
	for (int i = 0; i < 3; ++i)
	{
		g_mutex.lock();
		cout << std::this_thread::get_id() << "  " << g_i++ << endl;
		g_mutex.unlock();
	}
}
 
int main()
{
	thread t1(c);
	thread t2(c);
 
	if (t1.joinable())
		t1.join();//调用该函数会阻塞当前线程,直到该t1线程执行完成
	if (t2.joinable())
		t2.join();
    return 0;
}

三、基于CAS的原子类 

mutex互斥锁毕竟比较重,对于系统消耗有些大,C++11的thread类库提供了针对简单类型的原子操作类,如std::atomic_int,atomic_long,atomic_bool等,它们值的增减都是基于CAS操作的,既保证了线程安全,效率还非常高

下面代码示例开启10个线程,每个线程对整数增加1000次,保证线程安全的情况下,应该加到10000次,这种情况下,可以用atomic_int来实现,代码示例如下:

#include <iostream>
#include <atomic> // C++11线程库提供的原子类
#include <thread> // C++线程类库的头文件
#include <vector>

// 原子整形,CAS操作保证给sum自增自减的原子操作
std::atomic_int sum = 0;

// 线程函数
void sumTask()
{
    // 每个线程给sum加1000次
    for (int i = 0; i < 1000; ++i)
    {
        sum++;
    }
};

int main()
{
    // 创建10个线程放在容器当中
    std::vector<std::thread> vec;
    for (int i = 0; i < 10; ++i)
    {
        vec.push_back(std::thread(sumTask));
    }

    // 等待线程执行完成
    for (int i = 0; i < vec.size(); ++i)
    {
        vec[i].join();
    }

    // 所有子线程运行结束,sum的结果每次运行应该都是10000
    std::cout << "sum : " << sum << std::endl;

	return 0;
}

四、线程的同步通信

多线程在运行过程中,各个线程都是随着操作系统的调度算法,占用CPU时间片来执行指令做事情,每个线程的运行完全没有顺序可言。但是在某些应用场景下,一个线程需要等待另外一个线程的运行结果,才能继续往下执行,这就需要涉及线程之间的同步通信机制。

线程间同步通信最典型的例子就是生产者-消费者模型,生产者线程生产出产品以后,会通知消费者线程去消费产品;如果消费者线程去消费产品,发现还没有产品生产出来,它需要通知生产者线程赶快生产产品,等生产者线程生产出产品以后,消费者线程才能继续往下执行。

C++11 线程库提供的条件变量condition_variable,就是Linux平台下的Condition Variable机制,用于解决线程间的同步通信问题,下面通过代码演示一个生产者-消费者线程模型,仔细分析代码:

#include <iostream>           // std::cout
#include <thread>             // std::thread
#include <mutex>              // std::mutex, std::unique_lock
#include <condition_variable> // std::condition_variable
#include <vector>

// 定义互斥锁(条件变量需要和互斥锁一起使用)
std::mutex mtx;
// 定义条件变量(用来做线程间的同步通信)
std::condition_variable cv;
// 定义vector容器,作为生产者和消费者共享的容器
std::vector<int> vec;

// 生产者线程函数
void producer()
{
	// 生产者每生产一个,就通知消费者消费一个
	for (int i = 1; i <= 10; ++i)
	{
		// 获取mtx互斥锁资源
		std::unique_lock<std::mutex> lock(mtx);

		// 如果容器不为空,代表还有产品未消费,等待消费者线程消费完,再生产
		while (!vec.empty())
		{
			// 判断容器不为空,进入等待条件变量的状态,释放mtx锁,
			// 让消费者线程抢到锁能够去消费产品
			cv.wait(lock);
		}
		vec.push_back(i); // 表示生产者生产的产品序号i
		std::cout << "producer生产产品:" << i << std::endl;

		/* 
		生产者线程生产完产品,通知等待在cv条件变量上的消费者线程,
		可以开始消费产品了,然后释放锁mtx
		*/
		cv.notify_all();

		// 生产一个产品,睡眠100ms
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}
// 消费者线程函数
void consumer()
{
	// 消费者每消费一个,就通知生产者生产一个
	for (int i = 1; i <= 10; ++i)
	{
		// 获取mtx互斥锁资源
		std::unique_lock<std::mutex> lock(mtx);

		// 如果容器为空,代表还有没有产品可消费,等待生产者生产,再消费
		while (vec.empty())
		{
			// 判断容器为空,进入等待条件变量的状态,释放mtx锁,
			// 让生产者线程抢到锁能够去生产产品
			cv.wait(lock);
		}
		int data = vec.back(); // 表示消费者消费的产品序号i
		vec.pop_back();
		std::cout << "consumer消费产品:" << data << std::endl;

		/*
		消费者消费完产品,通知等待在cv条件变量上的生产者线程,
		可以开始生产产品了,然后释放锁mtx
		*/
		cv.notify_all();

		// 消费一个产品,睡眠100ms
		std::this_thread::sleep_for(std::chrono::milliseconds(100));
	}
}
int main()
{
	// 创建生产者和消费者线程
	std::thread t1(producer);
	std::thread t2(consumer);

	// main主线程等待所有子线程执行完
	t1.join();
	t2.join();

	return 0;
}

参考:

C++11 - thread多线程编程,线程互斥和同步通信,死锁问题分析解决_大秦坑王的专栏-CSDN博客

《深入应用C++11》笔记-线程std::thread_WizardtoH-CSDN博客

std::thread详解 - _yanghh - 博客园

多线程 - 从 pthread 转换到 std::thread_个人文章 - SegmentFault 思否

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++11的 thread多线程 的相关文章

随机推荐

  • Cesium开发基础 获取网上的3dtiles数据(仅做测试数据,不得商用)

    Cesium开发基础 获取网上的3dtiles数据 仅做测试数据 不得商用 本程序及利用本程序获得的资源严禁在网络上公开传播及用于商业用途 对于使用不当造成的法律后果 本程序的开发者 本文章的作者不承担任何连带责任 首先下载该程序 http
  • 应用SVM预测澳大利亚降雨(含数据预处理与调参)

    0 声明 本文主要内容来自视频 2020机器学习全集 菜菜的sklearn完整版 价值4999元的最全机器学习sklearn全集 赶紧收藏 哔哩哔哩 bilibili 课件来自 https pan baidu com s 1Xl4o0PMA
  • 操作系统——文件管理

    0 关注博主有更多知识 操作系统入门知识合集 目录 9 1文件系统概念 思考题 9 2文件的物理结构 思考题 9 3文件存储和目录 9 1文件系统概念 文件的定义 文件是计算机信息存取的一种重要组织形式 文件由若干信息项有序构成 信息项可以
  • html2pdf 一键生成pdf实践

    背景 项目中需要将查询的图表及表格一键生成pdf 便于运营查看操作 调研 经调研 目前社区有合适的插件实现 html2pdf js 该插件的实现原理就是将html内容获取 解析 用cavans画出来 生成图片 然后由图片生成pdf文件 使用
  • 子组件向父组件传值

    子组件 div style margin top 20px font size 14px span 已经拥有账户 span span span div
  • Qt 屏蔽qDebug 输出

    在pro 文件中定义 QT NO DEBUG OUTPUT 这个宏 就可以屏蔽qDebug 的输出了 DEFINES QT NO DEBUG OUTPUT 那么为什么定义这个宏就可以屏蔽qDebug 的输出呢 看qlogging h 中的定
  • origin设置不同区域的颜色_半分钟教程:关于 Origin 中 Error Bar,看这篇教程就够了...

    本教程转载自 Originlab 帮助中心 前 言 几年前 小编写过一篇关于 Error Bar 的小教程 Error Bar其实一点也不Error 文中介绍了 Error Bar 的基本使用方法 但还有一些内容没能介绍完全 比如如何添加不
  • layui 监听多选框(checkbox) 点击事件

    html代码
  • 关于MIPI协议(一)——物理层D-PHY总结

    关于MIPI协议 一 物理层D PHY总结 通读了一下MIPI物理层D PHY的规格书 拿掉了规格书中一些冗余繁复的部分 留下了一些比较重要的内容 现总结如下 规格书中前部分章节大篇幅论述了D PHY的几种内部实现结构 D PHY采用的是非
  • [Heat]Heat中资源的扩展与加载

    Heat的核心是stack 从某方面讲 heat就是围绕stack的生命周期在玩 stack又是由各种各样的资源组成的 heat除了自定义的大量资源外 还允许用户自定义自己需要的资源 提供了很强的扩展性 下面将简单分析heat资源加载的流程
  • Oracle---排序、分组、表连接

    文章目录 一 排序查询 二 分组查询 三 多表关联查询 表连接 1 内连接 2 外连接 3 自然连接 一 排序查询 使用ORDER BY子句排序 ASC 升序 默认情况下 DESC 降序 ORDER BY 子句在SELECT语句的结尾 例1
  • Mybatis Generator 逆向生成器

    一 首先 安装eclipse插件 Help Eclipser Marketplace中查找 Mybatis Generator 1 3 5安装 二 新建project New other 查找如下 点击next 选择你要将文件生成到哪里的项
  • avalon视频学习笔记(五)

    一 插入移除处理 1 绑定属性ms if a 当ms if的值为真 true 时 就将元素输出到页面里面 div div 根据条件显示内容 div div div div 根据条件显示内容 div div b 如果当ms if 的值为假 f
  • Pytorch c++ 部署报错解决方案

    目录 1 Only the versions between 2017 and 2019 inclusive are supported 2 Cannot find cuDNN library Turning the option off
  • Linux firewall(防火墙)相关命令

    1 检验防火墙是否启动 firewall cmd state 2 检查8080 端口是否启动 firewall cmd permanent zone public list ports 3 开启 8080 端口 firewall cmd z
  • 离群点检测算法python代码

    很高兴为您回答问题 在python中 可以使用以下几种方法实现离群点检测算法 Z score方法 该方法通过计算数据的Z score来识别离群点 您可以使用scipy库中的zscore函数来计算Z score IQR方法 该方法通过计算数据
  • 黄金矿工-Python

    首先做这个项目之前得清楚需要的材料和这个项目的结构 我自己感觉这个游戏项目的难点就在于钩子摆动的问题 它需要利用到三角函数来实现出来 我利用了pygame transform rotate 方法来实现钩字摇摆的问题 用sin和cos的方法来
  • ​Learun FrameWork 强大工作流引擎,让OA更智能

    互联网的发展促使企业在信息化的道路上不断探索 而随着企业信息化进程的不断深入 OA协同办公的概念也逐步进入大众的视野 OA的选型关乎企业的生存发展 除了需要重视 OA技术 OA品牌 OA产品 OA服务 四大要素之外 更重要的其实是让OA变得
  • Llama2下载流程与报错:download.sh: [[: not found Downloading LICENSE and Acceptable Usage Policy..

    最近Meta的新模型LlamaV2可谓是火出圈了 第一时间我也尝试下载了权重 下载Llama2需要首先取得许可 不过没有门槛 秒批 https ai meta com resources models and libraries llama
  • C++11的 thread多线程

    一 C 11的多线程类thread C 11之前 C 库中没有提供和线程相关的类或者接口 因此在编写多线程程序时 Windows上需要调用CreateThread创建线程 Linux下需要调用clone或者pthread create来创建