C++多线程编程:深入剖析std::thread的使用方法

C++11多线程thread

  • 一、线程thread
    • 1.1、语法
      • 1.1.1、构造函数
      • 1.1.2、主要成员函数
    • 1.2、简单线程的创建
    • 1.3、线程封装
    • 1.4、std::this_thread
      • 1.4.1、std::this_thread::get_id()
      • 1.4.2、std::this_thread::yield()
      • 1.4.3、std::this_thread::sleep_for
  • 总结

一、线程thread

std::thread#include头文件中声明,因此使用 std::thread 时需要包含 #include头文件。

1.1、语法

1.1.1、构造函数

(1)默认构造函数。
创建一个空的 thread 执行对象。

thread() _NOEXCEPT
{
	// construct with no thread
	_Thr_set_null(_Thr);
}

(2)初始化构造函数。
创建std::thread执行对象,该thread对象可被joinable,新产生的线程会调用threadFun函数,该函 数的参数由 args 给出。

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

&&表示既可以传入左值也可以传入右值。

(3)拷贝构造函数。

// 如果拷贝构造函数(被禁用),意味着 thread 不可被拷贝构造。 
thread(const thread&) = delete;

(4)move构造函数 。

thread(thread&& x)noexcept

move 构造函数,调用成功之后 x 不代表任何 thread 执行对象。
注意:可被 joinable 的 thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached。

示例:

#include 
#include 

using namespace std;

void thread_func(int &a)
{
	cout << "thread_func: a = " << (a += 10) << endl;
}

int main()
{
	int x = 10;
	thread t1(thread_func, ref(x));
	thread t2(move(t1)); // t1 线程失去所有权

	thread t3;
	t3 = move(t2); // t2 线程失去所有权

	// t1.join(); //执行会报错:已放弃 (核心已转储)
	t3.join();

	cout << "main end: x = " << x << endl;

	return 0;
}

执行结果:

thread_func: a = 20
main end: x = 20

1.1.2、主要成员函数

(1)get_id():获取线程ID,返回类型std::thread::id对象。
(2)joinable():判断线程是否可以加入等待。
(3)join():等该线程执行完成后才返回。
(4)detach():
detach调用之后,目标线程就成为了守护线程,驻留后台运行,与之关联的std::thread对象失去对目标线程的关联,无法再通过std::thread对象取得该线程的控制权。当线程主函数执行完之后,线程就结束了,运行时库负责清理与该线程相关的资源。

调用 detach 函数之后:

  1. *this 不再代表任何的线程执行实例。
  2. joinable() == false
  3. get_id() == std::thread::id()

1.2、简单线程的创建

使用std::thread创建线程,提供线程函数或者函数对象,并可以同时指定线程函数的参数。

  1. 传入0个值
  2. 传入2个值
  3. 传入引用
  4. 传入类函数
  5. detach
  6. move

(1)传入0个值:

#include 
#include 

using namespace std;

void thread_func1()
{
	cout << "thread_func1()" << endl;
}

int main()
{
	
	thread t1(&thread_func1);	// 只传递函数
	t1.join();					// 阻塞等待线程函数执行结束

	return 0;
}

(2)传入2个值:

#include 
#include 

using namespace std;

void thread_func2(int a, int b)
{
	cout << "thread_func2(): a + b =" << a + b << endl;
}

int main()
{
	int a = 10;
	int b = 16;

	thread t2(thread_func2, a, b);
	t2.join();

	return 0;
}

(3)传入引用:

#include 
#include 

using namespace std;

void thread_func3(int &c)
{
	cout << "thread_func3(): &c = " << &c 
	cout << " --> c + 10 =" << (c += 10) << endl;
}

int main()
{
	int c = 10;
	
	thread t3(thread_func3, ref(c));
	t3.join();
	cout << "main --> 3 : &c = " << &c << ", c = " << c << endl;

	return 0;
}

(4)传入类函数:

#include 
#include 

using namespace std;

class A
{
public:
	void func4(int a)
	{
		cout << "thread:" << name_ << ", fun4 a = " << a << endl;
	}
	void setName(string name) 
	{ 
		name_ = name; 
	}
	void displayName() 
	{ 
		cout << "this:" << this << ", name:" << name_ << endl; 
	}
	void play() 
	{ 
		std::cout << "play call!" << std::endl; 
	} 

private: 
	string name_;
};


int main()
{
	cout << "test--------------------------" << endl;
	A *a_ptr = new A();
	a_ptr->setName("hello,C++11");
	thread t4(A::func4, a_ptr, 10);
	t4.join();
	delete a_ptr;
	
	A *a_ptr2 = new A();
	a_ptr2->setName("hello,C++14");
	thread t42(&A::func4, a_ptr2, 10);// 传入类的函数地址、类地址、参数
	t42.join();
	delete a_ptr;

	return 0;
}

最好使用取地址符&的方式传入类函数,避免兼容问题。

(5)detach() :
将子线程从主线程中分离出来,主线程不再具有管理此子线程的能力。

#include 
#include 

using namespace std;

void thread_func5()
{
	cout << "func5 into sleep " << endl;
	this_thread::sleep_for(chrono::seconds(2));
	cout << "func5 leave " << endl;
}


int main()
{
	thread t5(&thread_func5);
	t5.detach();
	// t5.join() // 抛出异常
	cout << "t5 id : " << t5.get_id() << endl; // 抛出异常
	cout << "t5 joinable: " << t5.joinable() << endl;

	return 0;
}

执行结果:

t5 id : thread::id of a non-executing thread
t5 joinable: 0

(6)std::move() :
线程所有权转移。

#include 
#include 

using namespace std;

int main()
{

	thread t6(func6);
	thread t7(move(t6));
	//t6.join(); // 抛出异常 
	
	cout << "t6 id : " << t6.get_id() << endl;
	cout << "t6 joinable: " << t6.joinable() << endl;
	cout << "t7 joinable: " << t7.joinable() << endl;
	t7.join();

	return 0;
}

执行结果:

t6 id : thread::id of a non-executing thread
t6 joinable: 0
t7 joinable: 1
this is func6 !

1.3、线程封装

封装线程,子类能继承,然后子类能实现具体的业务逻辑。创建线程通过new来实现,参数列表和使用构造函数创建是一样的。

ower_thread.h

#ifndef _OWER_THREAD_H_
#define _OWER_THREAD_H_

#include 

class Ower_Thread {
public:
	Ower_Thread(); // 构造函数
	virtual ~Ower_Thread(); // 析构函数
	bool start(); 
	void stop(); 
	bool isAlive() const; // 线程是否存活

	std::thread::id id()
	{
		return th_->get_id();
	}
	std::thread* getThread()
	{
		return th_;
	}

	void join();	// 等待当前线程结束, 不能在当前线程上调用
	void detach();	//能在当前线程上调用
	static size_t CURRENT_THREADID();

protected: 
	void threadEntry(); 
	virtual void run() = 0; // 运行 
protected: 
	bool running_; //是否在运行 
	std::thread *th_;
};

#endif

ower_thread.cc

#include "ower_thread.h"
#include 
#include 
#include 

Ower_Thread::Ower_Thread() :
	running_(false), th_(NULL)
{

}

Ower_Thread::~Ower_Thread()
{
	if (th_ != NULL)
	{
		//如果到调用析构函数的时候,调用者还没有调用join则触发detach,此时是一个比较危险的动 作,用户必须知道他在做什么
		if (th_->joinable())
		{
			std::cout << "~Ower_Thread detach"<<std::endl; 
			th_->detach();
		}
		delete th_;
		th_ = NULL;
	}
	std::cout << "~Ower_Thread()" << std::endl;
}

bool Ower_Thread::start()
{
	if (running_)
	{
		return false;
	}
	try
	{
		th_ = new std::thread(&Ower_Thread::threadEntry, this);
	}
	catch (...)
	{
		throw "[Ower_Thread::start] thread start error";
	}
	return true;
}

void Ower_Thread::stop()
{
	running_ = false;
}

bool Ower_Thread::isAlive()const
{
	return running_;
}

void Ower_Thread::join() 
{
	if (th_->joinable()) 
	{
		th_->join(); // 不是detach才去join 
	} 
}

void Ower_Thread::detach()
{ 
	th_->detach(); 
}

size_t Ower_Thread::CURRENT_THREADID()
{
	// 声明为thread_local的本地变量在线程中是持续存在的,不同于普通临时变量的生命周期, 
	// 它具有static变量一样的初始化特征和生命周期,即使它不被声明为static。
	static thread_local size_t threadId = 0;
	if (threadId == 0)
	{
		std::stringstream ss;
		ss << std::this_thread::get_id();
		threadId = strtol(ss.str().c_str(), NULL, 0);
	}

	return threadId;
}


void Ower_Thread::threadEntry()
{
	running_ = true;
	try
	{
		run();
	}
	catch (std::exception &ex)
	{
		running_ = false;
		throw ex;
	}
	catch (...)
	{
		running_ = false;
		throw;
	}
	running_ = false;
}

test.cc

#include 
#include 
#include "ower_thread.h"

using namespace std;

class A:public Ower_Thread
{
public:
	void run()
	{
		while (running_)
		{
			cout << "class A" << endl;
			this_thread::sleep_for(chrono::seconds(5));
		}
		cout << "----- leave class A " << endl;
	}


};

class B :public Ower_Thread
{
public:
	void run()
	{
		while (running_)
		{
			cout << "class B" << endl;
			this_thread::sleep_for(chrono::seconds(2));
		}
		cout << "----- leave class B " << endl;
	}


};


int main()
{
	A a;
	a.start();
	B b;
	b.start();

	this_thread::sleep_for(chrono::seconds(5));

	a.stop();
	a.join(); // 需要我们自己join
	b.stop();
	b.join(); // 需要我们自己join

	cout << "Hello World!" << endl;

	return 0;
}

编译:

g++ -o my_thread test.cc ower_thread.cc -lpthread -std=c++11

执行结果:

$ ./my_thread
class B
class A
class B
class B
class A
class B
class B
----- leave class A 
----- leave class B 
Hello World!
~Ower_Thread()
~Ower_Thread()

1.4、std::this_thread

此命名空间对一组访问当前线程的函数进行分组。

功能 含义
get_id 获取线程 ID(函数)
yield 让出CPU
sleep_until 睡眠到时间点(功能)
sleep_for 睡眠时间跨度(功能)

1.4.1、std::this_thread::get_id()

功能:获取线程 ID。
返回:返回调用线程的线程 ID,此值唯一标识线程。成员类型的对象唯一标识线程thread::id。
示例:

// thread::get_id / this_thread::get_id
#include        // std::cout
#include          // std::thread, std::thread::id, std::this_thread::get_id
#include          // std::chrono::seconds
 
std::thread::id main_thread_id = std::this_thread::get_id();

void is_main_thread() {
  if ( main_thread_id == std::this_thread::get_id() )
    std::cout << "This is the main thread.\n";
  else
    std::cout << "This is not the main thread.\n";
}

int main() 
{
  is_main_thread();
  std::thread th (is_main_thread);
  th.join();
}

1.4.2、std::this_thread::yield()

调用线程生成,为实现提供了重新调度的机会。当线程等待其他线程前进而不阻塞时,应调用此函数。
示例:

// this_thread::yield example
#include        // std::cout
#include          // std::thread, std::this_thread::yield
#include          // std::atomic

std::atomic<bool> ready(false);

void count1m(int id) {
	while (!ready) {             // wait until main() sets ready...
		std::this_thread::yield();
	}
	for (volatile int i = 0; i<1000000; ++i) {}
	std::cout << id;
}

int main()
{
	std::thread threads[10];
	std::cout << "race of 10 threads that count to 1 million:\n";
	for (int i = 0; i<10; ++i) threads[i] = std::thread(count1m, i);
	ready = true;               // go!
	for (auto& th : threads) th.join();
	std::cout << '\n';

	return 0;
}

1.4.3、std::this_thread::sleep_for

阻止在指定的时间段内执行调用线程。
当前线程的执行将停止,直到至少从现在开始。其他线程继续执行。

参数:
调用线程恢复执行的时间跨度。请注意,多线程管理操作可能会导致超出此范围的某些延迟。是表示特定相对时间的对象。

示例:

#include        // std::cout, std::endl
#include          // std::this_thread::sleep_for
#include          // std::chrono::seconds
 
int main() 
{
  std::cout << "countdown:\n";
  for (int i=10; i>0; --i) {
    std::cout << i << std::endl;
    std::this_thread::sleep_for (std::chrono::seconds(1));
  }
  std::cout << "Lift off!\n";

  return 0;
}

总结

编译时需要添加lpthread库。

本文将深入解析C++11中多线程编程的核心组件——thread的使用方法。通过详细的示例代码和实际场景的案例,帮助读者全面了解和掌握thread的功能和灵活性。文章将从thread的创建、启动和管理等方面展开讲解,同时介绍了如何处理线程间的通信和同步问题,以及如何利用thread提高程序的性能和并发能力。无论你是初学者还是有一定经验的开发者,本文都将帮助你深入理解C++11多线程编程中的thread,为你的代码提供更强大的并发能力和性能优化的可能。

C++多线程编程:深入剖析std::thread的使用方法_第1张图片

你可能感兴趣的:(C/C++技术干货,c++,开发语言,linux,thread,后端,c++11,多线程)