java 线程的构造函数_[c++11]多线程编程(二)——理解线程类的构造函数
构造函数的参数
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()'
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 f1 = std::bind(&A::func1, &a);
std::function f2 = std::bind(&A::func2, &a, 1);
std::function f3 = std::bind(&A::func2, &a, std::placeholders::_1);
std::function f4 = std::bind(&A::func3, &a, 1, std::placeholders::_1);
std::function 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
#include
#include
// 仿函数
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));
然后vs和g++都可以成功编译,而且子线程可以修改外部变量的值。
当然这样并不好,多个线程同时修改同一个变量,会发生数据竞争。
同理,构造函数的第一个参数是可调用对象,默认情况下其实传递的还是一个副本。
#include
#include
#include
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内部已经有一个线程在管理了
参考
java 线程的构造函数_[c++11]多线程编程(二)——理解线程类的构造函数相关推荐
- c++11 多线程编程(二)------ 线程类构造函数深入理解
构造函数的参数 std::thread类的构造函数是使用可变参数模板实现的,也就是说,可以传递任意个参数,第一个参数是线程的入口函数,而后面的若干个参数是该函数的参数. 第一参数的类型并不是c语言中的 ...
- 多线程编程定长线程池
多线程编程 定长线程池,可控制线程最大并发数,超出的线程会在队列中等待 Executors的方式创建定长线程池(不推荐容易,容易内存溢出OOM) ThreadPoolExecutor构造函数创建定长线 ...
- 多线程编程学习笔记——线程池(二)
接上文 多线程编程学习笔记--线程池(一) 三.线程池与并行度 此示例是学习如何应用线程池实现大量的操作,及与创建大量线程进行工作的区别. 1. 代码如下 using System; using Sy ...
- Linux与C++11多线程编程(学习笔记)
多线程编程与资源同步 在Windows下,主线程退出后,子线程也会被关闭; 在Linux下,主线程退出后,系统不会关闭子线程,这样就产生了僵尸进程 3.2.1创建线程 Linux 线程的创建 #inc ...
- Java多线程系列(二):线程的五大状态,以及线程之间的通信与协作
在Java面试的时候,经常会问到Java并发编程相关的多线程.线程池.线程锁.线程通信等面试必考点,比如: Java并发编程系列:Java线程池的使用方式,核心运行原理.以及注意事项 Java并发编程 ...
- Java 多线程(二)—— 线程的同步
上文创建多线程买票的例子中注释会出现错票.重票的问题,本文来讲讲如何解决此问题.本文例子:利用多线程模拟 3 个窗口卖票 实现Runnable接口 public class TestThread2 ...
- java 多线程编程指南 pdf_Java-多线程编程
Java 给多线程编程提供了内置的支持. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务. 多线程是多任务的一种特别的形式,但多线程使用了更小的资源开 ...
- c语言中线程的特性,具有C 11多线程的特征库
OP的问题引起了我的注意,因为通过多线程获得的加速数字运算是我个人名单上的顶级待办事项之一. 我必须承认,我对Eigen库的经验非常有限. (我曾经将3×3旋转矩阵的分解用于欧拉角,这在特征库中以一般 ...
- java死锁业务场景_【深入浅出多线程系列十二】:什么是死锁?(场景+代码示例)...
在学习Java的道路上,是否路过多线程时总让你很迷惘:很不巧,我也是,而使我们感到很迷惘主要原因都源于没有对概念的深深的理解和实践.所以我决定漫步Java多线程,同你一起会会多线程. 多线程系列 多线 ...
最新文章
- mysql load xml_MySQL的SQL语句 - 数据操作语句(10)- LOAD XML 语句
- 二分类神经网络的特征光谱---2-3至2-9
- c语言程序的标识符分类,华中科技大学计算机学院C语言程序设计标识符.PPT
- Redis-cluster架构
- Neutron 如何支持多种 network provider - 每天5分钟玩转 OpenStack(70)
- 老罗学习MVC之旅:MVC组件分析
- 【设计模式】工厂模式 Factory Pattern
- React Native – 使用 JavaScript 开发原生应用
- 【杂谈】我学习这么好,为什么找不到工作?
- 阿铭Linux_网站维护学习笔记20190227
- Redis Hget 命令
- 常用测速网站及工具(IPv4/IPv6)
- 矩阵转置行列式的运算规律
- Java获取当前时间到凌晨12点剩余秒数
- Windows Server 2008 R2 搭建网站详细教程
- Originpro绘制y轴偏移堆积图无法设置偏移量
- 写一个简单的python调用接口(API)
- css选择器 ~ (波浪号)、+(加号)、>(大于号)的用法解析和举例
- css盒模型、布局模型、 flex弹性盒模型及样式设置小技巧
- 关于编码(二):ASCII和Unicode