构造函数的参数

std::thread类的构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数,第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数

第一参数的类型并不是c语言中的函数指针(c语言传递函数都是使用函数指针),在c++11中,增加了可调用对象(Callable Objects)的概念,总的来说,可调用对象可以是以下几种情况:

  • 函数指针
  • 重载了operator()运算符的类对象,即仿函数
  • lambda表达式(匿名函数)
  • std::function

函数指针示例

// 普通函数 无参
void function_1() {
}// 普通函数 1个参数
void function_2(int i) {
}// 普通函数 2个参数
void function_3(int i, std::string m) {
}std::thread t1(function_1);
std::thread t2(function_2, 1);
std::thread t3(function_3, 1, "hello");t1.join();
t2.join();
t3.join();

实验的时候还发现一个问题,如果将重载的函数作为线程的入口函数,会发生编译错误!编译器搞不清楚是哪个函数,如下面的代码:

// 普通函数 无参
void function_1() {
}// 普通函数 1个参数
void function_1(int i) {
}
std::thread t1(function_1);
t1.join();
// 编译错误
/*
C:\Users\Administrator\Documents\untitled\main.cpp:39:
error: no matching function for call to 'std::thread::thread(<unresolved overloaded function type>)'std::thread t1(function_1);^
*/

仿函数

// 仿函数
class Fctor {
public:// 具有一个参数void operator() () {}
};
Fctor f;
std::thread t1(f);
// std::thread t2(Fctor()); // 编译错误
std::thread t3((Fctor())); // ok
std::thread t4{Fctor()}; // ok

一个仿函数类生成的对象,使用起来就像一个函数一样,比如上面的对象f,当使用f()时就调用operator()运算符。所以也可以让它成为线程类的第一个参数,如果这个仿函数有参数,同样的可以写在线程类的后几个参数上。

t2之所以编译错误,是因为编译器并没有将Fctor()解释为一个临时对象,而是将其解释为一个函数声明,编译器认为你声明了一个函数,这个函数不接受参数,同时返回一个Factor对象。解决办法就是在Factor()外包一层小括号(),或者在调用std::thread的构造函数时使用{},这是c++11中的新的同意初始化语法。

但是,如果重载的operator()运算符有参数,就不会发生上面的错误。

匿名函数

std::thread t1([](){std::cout << "hello" << std::endl;
});std::thread t2([](std::string m){std::cout << "hello " << m << std::endl;
}, "world");

std::function

class A{
public:void func1(){}void func2(int i){}void func3(int i, int j){}
};A a;
std::function<void(void)> f1 = std::bind(&A::func1, &a);
std::function<void(void)> f2 = std::bind(&A::func2, &a, 1);
std::function<void(int)> f3 = std::bind(&A::func2, &a, std::placeholders::_1);
std::function<void(int)> f4 = std::bind(&A::func3, &a, 1, std::placeholders::_1);
std::function<void(int, int)> f5 = std::bind(&A::func3, &a, std::placeholders::_1, std::placeholders::_2);std::thread t1(f1);
std::thread t2(f2);
std::thread t3(f3, 1);
std::thread t4(f4, 1);
std::thread t5(f5, 1, 2);

传值还是引用

先提出一个问题:如果线程入口函数的的参数是引用类型,在线程内部修改该变量,主线程的变量会改变吗?

代码如下:

#include <iostream>
#include <thread>
#include <string>// 仿函数
class Fctor {
public:// 具有一个参数 是引用void operator() (std::string& msg) {msg = "wolrd";}
};int main() {Fctor f;std::string m = "hello";std::thread t1(f, m);t1.join();std::cout << m << std::endl;return 0;
}// vs下: 最终是:"hello"
// g++编译器: 编译报错

事实上,该代码使用g++编译会报错,而使用vs2015并不会报错,但是子线程并没有成功改变外面的变量m

我是这么认为的:std::thread类,内部也有若干个变量,当使用构造函数创建对象的时候,是将参数先赋值给这些变量,所以这些变量只是个副本,然后在线程启动并调用线程入口函数时,传递的参数只是这些副本,所以内部怎么操作都是改变副本,而不影响外面的变量。g++可能是比较严格,这种写法可能会导致程序发生严重的错误,索性禁止了。

而如果可以想真正传引用,可以在调用线程类构造函数的时候,用std::ref()包装一下。如下面修改后的代码:

std::thread t1(f, std::ref(m));

然后vsg++都可以成功编译,而且子线程可以修改外部变量的值。

当然这样并不好,多个线程同时修改同一个变量,会发生数据竞争。

同理,构造函数的第一个参数是可调用对象,默认情况下其实传递的还是一个副本。

#include <iostream>
#include <thread>
#include <string>class A {
public:void f(int x, char c) {}int g(double x) {return 0;}int operator()(int N) {return 0;}
};void foo(int x) {}int main() {A a;std::thread t1(a, 6); // 1. 调用的是 copy_of_a()std::thread t2(std::ref(a), 6); // 2. a()std::thread t3(A(), 6); // 3. 调用的是 临时对象 temp_a()std::thread t4(&A::f, a, 8, 'w'); // 4. 调用的是 copy_of_a.f()std::thread t5(&A::f, &a, 8, 'w'); //5.  调用的是 a.f()std::thread t6(std::move(a), 6); // 6. 调用的是 a.f(), a不能够再被使用了t1.join();t2.join();t3.join();t4.join();t5.join();t6.join();return 0;
}

对于线程t1来说,内部调用的线程函数其实是一个副本,所以如果在函数内部修改了类成员,并不会影响到外面的对象。只有传递引用的时候才会修改。所以在这个时候就必须想清楚,到底是传值还是传引用!

线程对象只能移动不可复制

线程对象之间是不能复制的,只能移动,移动的意思是,将线程的所有权在std::thread实例间进行转移。

void some_function();
void some_other_function();
std::thread t1(some_function);
// std::thread t2 = t1; // 编译错误
std::thread t2 = std::move(t1); //只能移动 t1内部已经没有线程了
t1 = std::thread(some_other_function); // 临时对象赋值 默认就是移动操作
std::thread t3;
t3 = std::move(t2); // t2内部已经没有线程了
t1 = std::move(t3); // 程序将会终止,因为t1内部已经有一个线程在管理了

参考

  1. C++并发编程实战
  2. C++ Threading #8: Using Callable Objects

c++11 多线程编程(二)------ 线程类构造函数深入理解相关推荐

  1. java 线程的构造函数_[c++11]多线程编程(二)——理解线程类的构造函数

    构造函数的参数 std::thread类的构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数,第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数. 第一参数的类型并不是c语言中的 ...

  2. 详解Java多线程编程中LockSupport类的线程阻塞用法

    转载自  详解Java多线程编程中LockSupport类的线程阻塞用法 LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语.LockSupport实际 ...

  3. Linux与C++11多线程编程(学习笔记)

    多线程编程与资源同步 在Windows下,主线程退出后,子线程也会被关闭; 在Linux下,主线程退出后,系统不会关闭子线程,这样就产生了僵尸进程 3.2.1创建线程 Linux 线程的创建 #inc ...

  4. 多线程编程之三——线程间通讯

    七.线程间通讯 一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程和次要线程间要进行通信.这种线程间的通信不但是难以避免的,而 ...

  5. 多线程编程(2): 线程的创建、启动、挂起和退出

    python多线程编程(2): 线程的创建.启动.挂起和退出 如上一节,python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法.而创建 ...

  6. Python多线程编程之线程子类化

    Python多线程编程之线程子类化 基本思路 Threading模块简介 **MyThread**主要代码 实例 所有代码 运行结果 基本思路 导入Threading模块下的Thread类,将其子类化 ...

  7. C#多线程编程实例 线程与窗体交互

    C#多线程编程实例 线程与窗体交互 代码: public partial class Form1 : Form{//声明线程数组Thread[] workThreads = new Thread[10 ...

  8. 多线程编程:线程死锁的原因以及解决方法

    多线程编程:线程死锁的原因以及解决方法 关于线程死锁这个问题相信程序员在编写多线程程序时会经常遇到的一个经典问题,这种情况往往出现在多个线程同时对临界资源访问时所产生的. 属于临界资源的硬件有打印机. ...

  9. C++11多线程编程--线程创建

    参考资料 adam1q84 我是一只C++小小鸟 Thread support library Book:<C++ Concurrency in Action> 线程的创建 线程的创建有多 ...

最新文章

  1. 30个精美的模板,贺卡,图形圣诞素材
  2. 36 张图详解 DNS :网络世界的导航
  3. sqlite导入 mysql_Sqlite向MySql导入数据
  4. 养胃记住“红黄绿白黑”
  5. 提升PHP性能的21种方法
  6. 软件套装 推荐书籍-海洋工程类
  7. android申请权限一次性申请多个,android 6.0以上动态一次申请多个权限-最美解决方案...
  8. 协议森林14 逆袭 (CIDR与NAT)
  9. 4-什么是Image和container
  10. python在线包安装mysql_python安装mysql的依赖包mysql-python操作
  11. MySQL实验7存储过程_mysql的总结7--存储过程-阿里云开发者社区
  12. 2021-06-0贪吃蛇练习
  13. 【日常科普】浏览器网页视频自定义倍速播放(无需任何插件)
  14. linux 多个ftp站点,vsftp在虚拟主机上建立多个ftp站点
  15. 135编辑器的html,百度编辑器 整合135编辑器
  16. sort() sorted()
  17. 深入创新,共建原生 | 「DaoCloud 道客」与华钦科技签署合作备忘录
  18. 如是使用JS实现页面内容随机显示
  19. Python爬虫(scrapy模块、bs4模块) 爬取笔趣阁全本小说(三级页面)
  20. 【5 操作系统调度】

热门文章

  1. ADO.NET 之 一
  2. 开机自检BIOS语言详解
  3. MLT-type渲染算法review
  4. 【辨异】limit, limitation
  5. html向php传中文没有值,php - 为什么我的PHP / HTML表单没有向我发送数据 - SO中文参考 - www.soinside.com...
  6. 多个线程对串口读取 modbus_HART转Modbus转换器
  7. go和python对比的优缺点_Python与Golang对比
  8. linux使用running网卡ping,Linux CentOS 7 IP地址配置及网络问题排查
  9. pymysql ︱mysql的基本操作与dbutils+PooledDB使用
  10. R语言︱H2o深度学习的一些R语言实践——H2o包