首页 > 其他 > 详细

thread 基础

时间:2020-04-04 17:31:11      阅读:80      评论:0      收藏:0      [点我收藏+]

创建线程

线程函数

thread 可以和 callable 类型一起工作。

普通函数作为线程入口函数

#include <iostream>
#include <thread>		//@ 使用多线程接口需要引入thread头文件

void hello() {
	std::cout << "hello world from new thread" << std::endl;
}

int main()
{
	std::thread t(hello);  //@ 创建线程,指定线程入口函数
	t.join(); //@ 等待线程执行完成

	return 0;
}

lambda 表达式作为线程入口函数

thread t([] {std::cout << "hello world from new thread" << std::endl; });
t.join();

函数对象作为线程入口函数

class FunObj
{
public:
	void operator()(){  //@ 重载 operator ()
		std::cout << "hello world from new thread" << std::endl;
	}
};

int main()
{
	FunObj fun_obj;
	std::thread t(fun_obj);
	t.join();
	
	return 0;
}

类成员函数作为线程入口

class Greet
{
	const char* owner = "New Thread";
public:
	void SayHello(const char* name) {
		std::cout << "Hello " << name << " from " << owner << std::endl;
	}
};

int main()
{
	Greet greet;
	std::thread t(&Greet::SayHello, &greet, "C++ multi-threads");
	t.join();

	return 0;
}

也可以在类中重载 (),此时变为:

class Greet
{
	const char* owner = "New Thread";
public:
	void SayHello(const char* name) {
		std::cout << "Hello " << name << " from " << owner << std::endl;
	}
	void operator() (const char* name){   //@ 重载 operator ()
		std::cout << "Hello " << name << " from " << owner << std::endl;
	}
};

int main()
{
	Greet greet;
	std::thread t(greet, "C++ multi-threads"); //@ 传参时的变化
	t.join();

	return 0;
}

线程参数

当需要向线程传递参数时,可以直接通过 std::thread 的构造函数参数进行,构造函数通过完美转发将参数传递给线程函数。

void hello(std::string name) {
	std::cout << "hello " << name << std::endl;
}

int main()
{
	std::thread t(hello, "C++ 11 multi-threads");
	t.join();

	return 0;
}

传递引用参数

  • 向线程传递引用参数时,需要使用 std::ref。
  • 线程函数的参数必须也是引用类型,否则将会发生拷贝构造为函数参数创建一个副本。
class A
{
public:
	int m_i;  
	//@ 类型转换构造函数,可以把一个int转换成一个类A对象。
	A(int a) :m_i(a){
		std::cout << "A::A(int a)构造函数执行!" << this << 
			" thread id:" << std::this_thread::get_id() << std::endl;
	}
	A(const A &a) :m_i(a.m_i){
		std::cout << "A::A(A &a)复制构造函数执行!" << this 
			<< " thread id:" << std::this_thread::get_id() << std::endl;
	}

	~A(){
		std::cout << "A::~A()析构函数执行!" << this <<
			" thread id:" << std::this_thread::get_id() << std::endl;
	}
};

void myprint(A& pmybuf) //@ 使用引用作为参数,避免调用复制构造函数产生副本
{
	pmybuf.m_i = 199; 
	std::cout << "子对象myprint的参数地址是 " << &pmybuf << 
		" thread id: " << std::this_thread::get_id() << std::endl;// 打印的是pmybuf对象的地址
}

int main()
{
	std::cout << "main thread id: " << std::this_thread::get_id() << std::endl;
	A myobj(10); //@ 生成一个类对象
	thread mytobj(myprint, std::ref(myobj));
	mytobj.join();
	std::cout << myobj.m_i << std::endl;

	return 0;
}

传递 unique_ptr

void myprint(std::unique_ptr<int> pzn){
	std::cout << *pzn << std::endl;
}

int main()
{
	std::unique_ptr<int> myp(new int(100));
	thread mytobj(myprint2, std::move(myp));	//@ 必须使用 move 否则报错
	mytobj.join();

	return 0;
}

join 与 detach

必须指定 join 或 detach

一旦启动线程之后,我们必须决定是要等待直接它结束(通过join),还是让它独立运行(通过detach),必须二者选其一。如果在 thread 对象销毁的时候我们还没有做决定,则 thread 对象在析构函数中将调用 std::terminate() 从而导致我们的进程异常退出。

需要注意的是:在我们做决定的时候,很可能线程已经执行完了(例如上面的示例中线程的逻辑仅仅是一句打印,执行时间会很短)。

新的线程创建之后,究竟是新的线程先执行,还是当前线程的下一条语句先执行这是不确定的,因为这是由操作系统的调度策略决定的。

相关 API

API 注解
join 等待线程完成其执行
detach 允许线程独立执行
joinable 查询是否可以对该线程进行 join

join

  • join 之后,当前线程会一直阻塞,直到目标线程执行完成(当然,很可能目标线程在此处调用之前就已经执行完成了,不过这不要紧)。
  • join 之后,当子线程执行结束,主调线程将回收子调线程资源,并继续运行。
  • 如果目标线程的任务非常耗时,你就要考虑好是否需要在主线程上等待它了,因此这很可能会导致主线程卡住。
void loops()
{
	for (int i = 0; i < 10; i++) {
		std::cout << "this thread [" << std::this_thread::get_id()
			<< " ] print:" << i << std::endl;
	}
}

int main()
{
	std::thread t(loops);
	t.join();

	std::cout << "main thrad exit" << std::endl;
	return 0;
}

输出:

this thread [6244 ] print:0
this thread [6244 ] print:1
this thread [6244 ] print:2
this thread [6244 ] print:3
this thread [6244 ] print:4
this thread [6244 ] print:5
this thread [6244 ] print:6
this thread [6244 ] print:7
this thread [6244 ] print:8
this thread [6244 ] print:9
main thrad exit

detach

  • detach 是让目标线程成为守护线程(daemon threads)。
  • 一旦 detach ,目标线程将独立执行,即便其对应的 thread 对象销毁也不影响线程的执行。
  • 一旦 detach,主调线程无法再取得该被调线程的控制权。这个子线程将被 C++ 运行时库接管,当该线程执行结束的时候,由 C++ 运行时库负责回收该线程的资源。
void loops()
{
	for (int i = 0; i < 10; i++) {
		std::cout << "this thread [" << std::this_thread::get_id()
			<< " ] print:" << i << std::endl;
	}
}

int main()
{
	std::thread t(loops);
	t.detach();

	std::cout << "main thrad exit" << std::endl;
	return 0;
}

输出的结果是不定的,主线程退出以后,子线程不再输出到终端。

joinable

  • join 之后不能再被重复 join,反复 join 将出错。
  • detach 之后不能再进行 join,因为此时线程已经分离出去了,如果 detach 之后再 join 将出错。

管理当前线程

相关 API

下面的函数都在 std::this_thread 命名空间中。

API 注解
yield 让出处理器,重新调度各执行线程。通常用在自己的主要任务已经完成的时候,此时希望让出处理器给其他任务使用。
get_id 返回当前线程的线程 id
sleep_for 使当前线程的执行停止指定的时间段
sleep_until 使当前线程的执行停止直到指定的时间点

示例:

#include <iostream>
#include <thread>		
#include <chrono>
#include <sstream>
#include <ctime> 
#include <iomanip>

void print_time()
{
	using std::chrono::system_clock;
	auto in_time_t = system_clock::to_time_t(system_clock::now());

	std::stringstream ss;
	ss << std::put_time(localtime(&in_time_t), "%Y-%m-%d %X");
	std::cout << "now is: " << ss.str() << std::endl;
}

void sleep_thread()
{
	std::this_thread::sleep_for(std::chrono::seconds(3));
	std::cout << "[this thread: " << std::this_thread::get_id() <<
		" ] is waking up:";
	print_time();
	std::cout << std::endl;
}

void wait_until_thread()
{
	using std::chrono::system_clock;
	time_t time = system_clock::to_time_t(system_clock::now());
	struct std::tm *ptm = std::localtime(&time);
	ptm->tm_sec += 10;
	std::this_thread::sleep_until(system_clock::from_time_t(mktime(ptm)));
	std::cout << "[this thread: " << std::this_thread::get_id() <<
		" ] waiting until: ";
	print_time();
	std::cout << std::endl;
}

void loop_thread()
{
	for (int i = 0; i < 10; i++) {
		std::cout << "[this thread: " << std::this_thread::get_id() <<
			" ] print: " << i << std::endl;
	}
}

int main()
{
	print_time();

	std::thread t1(sleep_thread);
	std::thread t2(loop_thread);
	std::thread t3(wait_until_thread);

	t1.join();
	t2.join();
	t3.join();

	return 0;
}

输出:

now is: 2020-04-04 11:07:57
[this thread: 14784 ] print: 0
[this thread: 14784 ] print: 1
[this thread: 14784 ] print: 2
[this thread: 14784 ] print: 3
[this thread: 14784 ] print: 4
[this thread: 14784 ] print: 5
[this thread: 14784 ] print: 6
[this thread: 14784 ] print: 7
[this thread: 14784 ] print: 8
[this thread: 14784 ] print: 9
[this thread: 12208 ] is waking up:now is: 2020-04-04 11:08:00

[this thread: 21040 ] waiting until: now is: 2020-04-04 11:08:07

一次调用

API 注释
call_once 即便在多线程环境下,也能保证只调用某个函数一次
once_flag call_once配合使用

在一些情况下,我们有些任务需要执行一次,并且我们只希望它执行一次,例如资源的初始化任务。这个时候就可以用到上面的接口。这个接口会保证,即便在多线程的环境下,相应的函数也只会调用一次。

示例:

有三个线程都会使用 init 函数,但是只会有一个线程真正执行它。

#include <mutex>
#include <thread>
#include <iostream>


void init() {
	std::cout << "initializing..."<< std::endl;
}

void worker(std::once_flag* flag) {
	std::call_once(*flag, init);
}

int main()
{
	std::once_flag flag;

	std::thread t1(worker, &flag);
	std::thread t2(worker, &flag);
	std::thread t3(worker, &flag);

	t1.join();
	t2.join();
	t3.join();

	return 0;
}

我们无法确定具体是哪一个线程会执行 init。而事实上,我们也不关心,因为只要有某个线程完成这个初始化工作就可以了。

thread 基础

原文:https://www.cnblogs.com/xiaojianliu/p/12632306.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!