C++11——std::thread

线程的创建——线程的启动与结束


通过函数来创建一个线程


#include <iostream>
#include <thread>

// 线程入口函数
void function()
{
	std::cout << "The thread is start, thread_id = " << std::this_thread::get_id() << std::endl;
	//...do something
	std::cout << "The thread is end, thread_id = " << std::this_thread::get_id() << std::endl;
}

int main()
{
	std::cout << "The main thread is start, thread_id = " << std::this_thread::get_id() << std::endl;
	std::thread t1(function); // 创建了线程t1,其入口函数为function()
//	t1.detach();
	if (t1.joinable())
	{
		t1.join();
	}
	std::cout << std::thread::hardware_concurrency() << std::endl;
	return 0;
}

线程的启动:

当我们创建一个线程时,即上述代码中执行完 std::thread t1(function); 这段代码后,线程t1便开始启动了。

线程的结束:

当我们在主线程中调用t1.join()时,此时主线程阻塞,等待子线程t1执行结束返回后,继续执行,直到主线程结束。

当我们在主线程中调用t1.detach()时,此时子线程t1与主线程分离,主线程失去对子线程t1的控制权,子线程t1由系统后台进行管理控制,并由其释放线程相关资源。

注意事项:

一个线程一旦执行join()函数或detach()函数后,便不能再调用这两个函数了,因此当我们的代码非常长时,我们可以通过joinable()函数的返回值来判断能否调用这两个函数,若返回值为true则可调用,若返回值为false则不可调用。

在一个线程中,我们可以通过 "std::this_thread::get_id()" 函数来获取当前所在线程的id。


通过类对象来创建一个线程


#include <iostream>
#include <thread>

class Fctor
{
public:
	int &m_i;    // int &m_i;
	Fctor(int &i) : m_i(i) 
	{
		std::cout << "Fctor(int&)构造函数执行!" << std::endl;
	}
	Fctor(const Fctor &fct) : m_i(fct.m_i)
	{
		std::cout << "Fctor(const Fctor&)拷贝构造函数执行!" << std::endl;
	}
	~Fctor()
	{
		std::cout << "~Fctor()析构函数执行!" << std::endl;
	}
	void operator() ()
	{
		std::cout << "m_i = "<< m_i << std::endl;
	}
};

int main()
{
	int m_i = 521;
	Fctor fct(m_i);
	std::thread t1(fct);
	//t1.detach();
	if (t1.joinable())
	{
		t1.join();
	}
	return 0;
}

 注意事项:

我们发现,当我们通过类对象来创建子线程时,此时会存在这样一个问题,倘若类对象内包含诸如 "引用或指针" 这样的成员,那么我们如果在主线程中调用子线程的detach()函数,那么就有可能发生这样的一种情况,即主线程执行完毕,其管理的资源全部释放掉了,如上述代码中的整型类型 int m_i ,而子线程由系统后台进行管理,如果此时在子线程内部访问主线程中已经释放掉了的m_i整型变量,那么便会存在线程安全问题。所以我们要尽量避免这类事情发生,要么直接采用join()函数,让子线程在主线程结束前与其汇合,这样就不会存在子线程访问主线程中释放掉的资源的情况,另一种则是不要通过包含引用或者指针这种类对象来创建一个线程。

另一方面,我们发现,当我们执行 std::thread t1(fct); 这段代码时,是执行类Fctor的拷贝构造函数,将其拷贝对象给了子线程t1,在子线程中访问的都是该拷贝对象中的信息。


用lambda表达式创建一个线程


#include <iostream>
#include <thread>

int main()
{
	auto mylambda = [] {
		std::cout << "thread start!" << std::endl;
	};
	std::thread t1(mylambda);
	//t1.detach();
	if (t1.joinable())
	{
		t1.join();
	}
	return 0;
}

用成员函数来创建一个线程


#include <iostream>
#include <thread>

class A
{
public:
	void mythread_word(int num)
	{
		std::cout << "子线程执行mythread_word" << ", from thread_id = " << std::this_thread::get_id() << std::endl;
	}
};

int main()
{
	A a;
	int num = 1997;
	std::thread t1(&A::mythread_word, &a, num);
	//t1.detach();
	if (t1.joinable())
	{
		t1.join();
	}
	return 0;
}

  线程参数传递问题

下面我们先看一段代码:

#include <iostream>
#include <thread>

void myprint(const int &x, char *str)
{
	std::cout << x << str << std::endl;
}

int main()
{
	int x = 10;
	int &y = x;
	char s[] = "this is a test!";
	std::thread t1(myprint, x, s);
	//t1.detach();
	if (t1.joinable())
	{
		t1.join();
	}
	return 0;
}

在这段代码中,我们创建子线程t1的入口函数为myprint,包含两个参数分别为整型常量引用&x和字符指针*str,通过跟踪调试我们发现,子线程中的x地址,与主线程中的x地址并不相同,即myprint函数中的引用参数并没有效果,"const int &x" 等价于 "const int x",而对于指针str,主线程与子线程中的地址均相同,因此我们要绝对避免传入指针类型的参数(当我们采用detach()函数时)。

传参建议:

若传递类似int这种简单类型参数时,都是采用值传递,不要采用引用。若传递类对象时,避免隐式转换,在构建线程时就构建出临时类对象作为参数传递,并且线程入口函数对应的参数采用常量类引用类型(否则系统会额外拷贝构造一次类对象)。

TIPS:上述注意事项均是在使用detach()方法时要考虑的,若采用join()方法,就不会出现上述中局部变量失效导致线程对内存非法引用的问题。

#include <iostream>
#include <thread>
#include <string>

void myprint(const int &x, const string &str)
{
	std::cout << x << str << std::endl;
}

int main()
{
	int x = 10;
	char s[] = "this is a test!";
	std::thread t1(myprint, x, s);
	//t1.detach();
	if (t1.joinable())
	{
		t1.join();
	}
	std::cout << "x = " << x << std::endl;
	return 0;
}

std::ref && std::cref | | std::move

若我们非要把主线程中的实际对象传递到线程中去,并对其进行修改,而不是传入一个该对象的拷贝,我们C++11提供了这样的两种方法:

解释:

  • std::ref 用于包装按引用传递的值。 
  • std::cref 用于包装按const引用传递的值

切记倘若我们给线程中传递了引用,则一定要小心主线程资源释放,导致子线程内存访问失效的问题,故应尽量采用join()方法。

发布了37 篇原创文章 · 获赞 42 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_42570248/article/details/100810323