c++11中的多线程std::thread

news/2024/10/17 22:25:22/

c++11中的多线程std::thread

在c++11中提供了新的多线程的创建方式std::thread, 丰富了对于多线程的使用。 std::thread类的构造函数的格式如下所示:

thread() noexcept;       //default constructor   其中noexcept表示函数不会抛出异常,如果抛出异常程序就会终止template <class Fn, class... Args> 
explicit thread (Fn&& fn, Args&&... args);   //initialization constructor  explicit 表示不支持隐式转换thread (const thread&) = delete;        //copy constructor delete表示不生成默认拷贝构造函数,并且可以禁止使用某个函数,也就表示不可以使用一个线程初始化另一个线程thread (thread&& x) noexcept;    //move constructor

其包含了一个默认的构造函数和一个模板构造函数。该模板构造函数是explicit,代表不允许隐式转换。 其拷贝构造函数声明为delete,代表不可以使用一个线程初始化另一个线程。 其拥有一个移动构造函数。
对于模板构造函数,可以看到,其入参就和函数的入参很类似,因此创建一个线程的方式大大简化。在此之前,在linux平台,创建线程需要使用pthread_create函数,其要求将参数封装为void*指针, 这个在写代码的时候是不太方便的。

template <class Fn, class... Args> 
explicit thread (Fn&& fn, Args&&... args);

c++11中thread和pthread相比有如下的一些优点:

  • 跨平台,pthread只能用在POSIX系统上
  • 简单,易用,传参方便,过去pthread需要将数据传递给void*指针, c++11直接像函数传参一样传递参数
  • 提供了std::future,std::promise等高级功能
  • 风格上更加像c++, pthread更像c的。

和pthread相比, 也有一些缺点:

  • 没有实现读写锁
  • pthread提供的功能更加多,例如设置CPU的亲和性

使用普通函数创建线程

#include<iostream>
#include <thread>void myprint()
{std::cout<<"thread start to run" << std::endl;
}int main()
{std::thread th(myprint);th.join();std::cout << "main thread end" << std::endl;
}

使用仿函数创建线程

#include<iostream>
#include <thread>class Test
{
public:void operator()() const{std::cout << "function object starts to run" << std::endl;}
};int main()
{Test obj;std::thread th(obj);th.join();std::this_thread::sleep_for(std::chrono::milliseconds(300));std::cout << "main thread end" << std::endl;
}

使用lambda创建线程

#include<iostream>
#include <thread>int main()
{auto func = [](){std::cout << "lambda function thread start to run" << std::endl;};std::thread th(func);th.join();std::this_thread::sleep_for(std::chrono::milliseconds(300));std::cout << "main thread end" << std::endl;
}

使用detach进行分离线程

#include<iostream>
#include <thread>void myprint()
{std::cout<<"thread start to run" << std::endl;
}int main()
{std::thread th(myprint);th.detach();std::this_thread::sleep_for(std::chrono::milliseconds(300));std::cout << "main thread end" << std::endl;
}

detach分离线程,这种方式平常使用较少。也容易引用一些问题。

thread的传参

在上面的例子中,我们创建的函数都是没有入参的。下面我们讨论下如何传参并且需要注意的一些点。

#include <iostream>
#include <thread>class String
{
public:String(const char* cstr) { std::cout << "String()" << std::endl; }// 1String(const String& v){ std::cout << "String(const String& v)" << std::endl; }// 2String(const String&& v) noexcept{ std::cout << "String(const String&& v)" << std::endl; }// 3String& operator=(const String& v){ std::cout << "String& operator=(const String& v)" << std::endl; return *this; }};void test(int i, String const& s) {}int main()
{String s("hello");std::cout << "----------------" << std::endl;// 输出 1, 2std::thread t1(test, 3, s);//拷贝构造t1.join();std::cout << "----------------" << std::endl;// 输出 2, 2std::thread t2(test, 3, std::move(s));//移动构造t2.join();std::cout << "----------------" << std::endl;// 只输出 1std::thread t3(test, 3, "hello");//拷贝指针,构造函数t3.join();std::cout << "----------------" << std::endl;// 无输出std::thread t4(test, 3, std::ref(s));//无拷贝std::cout << "----------------" << std::endl;t4.join();
}

执行结果如下所示:

String()
----------------
String(const String& v)
----------------
String(const String&& v)
----------------
String()
----------------
----------------

第一个例子,(1) s被copy到了新的memory space里去,所以call的是copy constructor 输出了1。(2) 第一步的结果生成了一个rvalue,所以传参数去函数的时候用的是move constructor,所以输出了2。

第二个例子,(1) s被move到了新thread的memory space里,所以用的是move constructor,输出2。(2) 同上,输出2。

第三个例子,输出的是这个String(const char* cstr) { std::cout << “String()” << std::endl; }。(1) 你在这里copy过去的其实是一个const char*指针,所以第一步没任何输出。(2) 这时你用const char*来构造一个String,所以输出0.

第四个例子,你的一切活动都是指向最初的那个s,所以没有任何constructor被调用,所以不输出任何东西。

还是上面的例子,这里将test函数中的const去除void test(int i, String & s) {}, 会如何?

#include <iostream>
#include <thread>class String
{
public:String(const char* cstr) { std::cout << "String()" << std::endl; }// 1String(const String& v){ std::cout << "String(const String& v)" << std::endl; }// 2String(const String&& v) noexcept{ std::cout << "String(const String&& v)" << std::endl; }// 3String& operator=(const String& v){ std::cout << "String& operator=(const String& v)" << std::endl; return *this; }};void test(int i, String & s) {}int main()
{String s("hello");std::cout << "----------------" << std::endl;// 输出 1, 2std::thread t1(test, 3, s);//拷贝构造t1.join();std::cout << "----------------" << std::endl;// 输出 2, 2std::thread t2(test, 3, std::move(s));//移动构造t2.join();std::cout << "----------------" << std::endl;// 只输出 1std::thread t3(test, 3, "hello");//拷贝指针,构造函数t3.join();std::cout << "----------------" << std::endl;// 无输出std::thread t4(test, 3, std::ref(s));//无拷贝std::cout << "----------------" << std::endl;t4.join();
}

这里会看到无法通过编译。为什么?

一个实参从主线程传递到子线程的线程函数中,需要经过两次传递。第1次发生在std::thread构造时,此次参数按值并以副本形式被保存。第2次发生在向线程函数传递时,此次传递是由子线程发起,并将之前std::thread内部保存的副本以右值的形式(std::move())传入线程函数中的。

String & s 是不可以指向一个右值的。这里不熟悉的可以重新温故一下左值引用,右值引用。

形参T、const T&或T&& 可以接受右值, T &不可以接受右值。 因此如果函数形参是T &, 则传参时必须要使用std::ref


http://www.ppmy.cn/news/573016.html

相关文章

深入探究 ReentrantLock 的应用和原理

博主介绍&#xff1a; ✌博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家✌ Java知识图谱点击链接&#xff1a;体系化学习Java&#xff08;Java面试专题&#xff09; &#x1f495;&#x1f495; 感兴趣的同学可以收…

Ajax技术的秘密揭秘:异步传输,高效交互

文章目录 I. 什么是AjaxAjax的定义和起源Ajax与传统的Web应用程序之间的区别 II. Ajax的工作原理Ajax的基本原理Ajax如何通过异步传输实现无需刷新页面 III. Ajax的应用场景在Web应用程序中应用Ajax的优势Ajax在哪些场景中使用 IV. Ajax的组成部分和APIXHR对象FormData对象Fetc…

C# 中使用枚举转换时需要注意的坑点及解决方案

在使用枚举进行转换时&#xff0c;需要注意一些细节&#xff0c;否则可能会出现一些意外情况。本文将介绍一些在枚举转换中需要注意的坑点&#xff0c;并提供一些解决方案。 1、枚举从 int 值转换的坑 在将 int 值转换成枚举类型时&#xff0c;可能会遇到一些问题。即使 int …

nginx 前端及接口代理配置

以下为总结配置 我这一块配置为了习惯统一化 不管前端还是接口配置 location后面都带上斜杠。 前端代理配置 我比较常用的为alias方式 # 演示root和alias两种配置静态资源的区别server {listen 80;server_name localhost;# 用root方式&#xff0c;location中的路径会拼加到r…

给COS挂上nginx代理

目录 前言&#xff1a; 解决思路&#xff1a; Nginx代理配置 关键配置讲解&#xff1a; 附录 前言&#xff1a; 最近研发同学反馈本地无法连上线上测试的COS文件服务器。由于安全问题&#xff0c;研发同学连接公司内部服务都是通过自己的VPN&#xff1b;经过排查之后发现…

基于Docker MinIO整合Nginx搭建反向代理

基于Docker MinIO整合Nginx搭建反向代理 docker拉去镜像安装和配置就不说了 主要说一下配置反向代理 第一次使用minio我陷入了一个误区&#xff0c;将nginx的data目录挂载到了minio的文件目录&#xff0c;这样是可以通过nginx访问minio文件&#xff0c;但是没有任何意义&…

某医院nginx 前置机(反向代理)配置

数据流图 外网访问-http://13*.*.*.12*:8087/&#xff08;在出口做dnat&#xff09;----http://10.*.*.230:8087/&#xff08;前置机反向代理到内网&#xff09;----10.1.*.230:8087(内网) 在下配置在 10.*.*.230机器上进行配置&#xff1a; 一、安装nginx软件&#xff0c;目…

使用Nginx搭建反向代理

引言&#xff1a;最近公司有台服务器遭受DDOS攻击&#xff0c;流量在70M以上&#xff0c;由于服务器硬件配置较高所以不需要DDOS硬件防火墙。但我们要知道&#xff0c;IDC机房是肯定不 允许这种流量一直处于这么高的&#xff0c;因为没法具体知道后面陆续攻击的流量会有多大&am…