线程类的构造函数

构造函数的参数

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

第一参数的类型并不是c++中的函数指针(c++传递函数都是使用函数指针),在c++11中,增加了可调用对象(Callable Objects)的概念,总的来说,可调用对象可以是以下几种情况:
1.函数指针
2.重载了operator()运算符的类对象,即仿函数
3.lambda表达式(匿名函数)
4.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();

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

仿函数

class MyT
{
public:
	int x;
	MyT(int xx = 0) :x(xx) {}
	MyT(const MyT &obj) :x(obj.x) { cout << "拷贝构造函数被调用" << endl; }
	~MyT() { cout << "析构函数被调用" << endl; }
	void operator()(int i)
	{
		cout << "子线程2运行开始" << endl;
		cout << i << endl;
		cout << x << endl;
		cout << "子线程2运行结束" << endl;
	}
};

//使用函数对象
MyT myT(6);
thread mythread2(myT,1);//值传递,会调用一次拷贝构造函数
mythread2.join();

lambda表达式(匿名函数)

//使用lambda表达式
	auto my_lambda = [=]()->void
	{
		cout << "子线程3运行开始" << endl;

		cout << "子线程3运行结束" << endl;
		
	};
	thread mythread3(my_lambda);
	/*
	thread mythread3([=]()->void
	{
	cout << "子线程3运行开始" << endl;

	cout << "子线程3运行结束" << endl;

	});
	*/
	mythread3.join();

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));

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

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

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

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

第一次学习的完整代码如下:

#include "stdafx.h"
#include<iostream>
#include<map>
#include<vector>
#include<string>
#include<thread>
using namespace std;
class MyT
{
public:
	int x;
	MyT(int xx = 0) :x(xx) {}
	MyT(const MyT &obj) :x(obj.x) { cout << "拷贝构造函数被调用" << endl; }
	~MyT() { cout << "析构函数被调用" << endl; }
	void operator()(int i)
	{
		cout << "子线程2运行开始" << endl;
		cout << i << endl;
		cout << x << endl;
		cout << "子线程2运行结束" << endl;
	}
};
void myprint()
{
	cout << "子线程1运行开始" << endl;

	cout << "子线程1运行结束" << endl;
}
//detach:分离,即主线程不和子线程汇合了,各执行各的,主线程不必等待子线程运行结束后再执行
//一旦detach()之后,与这个主线程关联的thread对象就会失去与这个主线程的关联,此时这个子线程会驻留在后台运行
//这个子线程就相当于被运行时库接管,当这个子线程执行结束后,由运行时库负责清理该线程相关的资源(相当于linux的守护线程)
//一旦调用detach(),就不能再调用join()

//joinable():判断是否可以成功使用join()或detach()

//二.其他创建线程的手法
//(2.1)用类,即使用函数对象!
//(2.2)用lambda表达式


int main()
{
	thread mythread1(myprint);//(1)创建了子线程,子线程的执行起点(入口)myprint();(2)myprint线程开始执行
	mythread1.join();//阻塞主线程,等待子线程执行完毕,然后主、子线程汇合
	//mythread.detach();

	//使用函数对象
	MyT myT(6);
	thread mythread2(myT,1);//值传递,会调用一次拷贝构造函数
	mythread2.join();

	//使用lambda表达式
	auto my_lambda = [=]()->void
	{
		cout << "子线程3运行开始" << endl;

		cout << "子线程3运行结束" << endl;
		
	};
	thread mythread3(my_lambda);
	/*
	thread mythread3([=]()->void
	{
	cout << "子线程3运行开始" << endl;

	cout << "子线程3运行结束" << endl;

	});
	*/
	mythread3.join();


	cout << "主线程运行结束" << endl;



	system("pause");
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值