C++11中的多线程开发

前言:我一直在Windows下开发,所以对Linux下线程的使用不甚了解,不过想来思路是一样的,主要是接口和细节上的差异。我喜欢多线程,主要是可以大幅提高产品的性能。在没有C++11之前,线程开发都是要调用WindowsAPI,还有很多繁琐的参数,线程模式单一,数据传递也不方便。但是C++11的出现,让多线程开发变得非常简单。本文总结一下我在使用C++11线程库中的经验。

一、线程类

头文件:<thread>

类:thread

命名空间:std

thread类是实现多线程的主类,C++11的多线程开发全是围绕thread展开,而条件变量、互斥量都是为线程服务的。

1、线程启动

创建线程对象时向构造函数传入线程函数,线程就会启动

例:void fun(){ printf("working");}

thread(fun).detach();

可以作为线程函数的不止普通函数,还可以是匿名函数、类成员函数、函数对象、function类对象等。

//类成员函数做线程函数
class Task
{
public:
	void Working(){ printf("i'm working"); };
};
Task t;
thread thd = thread(&Task::Working, t);

//匿名函数做线程函数
thread thd = thread([](){ printf("i'm working"); });
以上两种方式是最常用的线程函数。

需要注意的是,如果用类成员函数作为线程函数,则第二个参数必须指定相应的类对象。

类成员函数作为线程函数的好处:可以先在类中做其他工作,在必要时才启动线程,而且线程函数不必是静态函数,所以线程函数中可以直接使用当前实例的成员变量和成员函数(这和用静态函数做回调函数不同)。

匿名函数作为线程函数的好处:对于只使用一次的函数,尤其是函数体很小的时候,用匿名函数可以节约代码,清晰代码逻辑,而且匿名函数的过滤条件允许它使用当前类对象的成员变量和/或当前函数的局部变量。

使用匿名函数作为线程函数时必须注意的问题是:如果线程中用到当前函数的局部变量,则因为生命周期的原因,在线程执行过程中,这些局部变量可能已经因为函数执行完毕而被释放,所以匿名函数最好使用类成员变量,因为一般线程的生命周期要小于当前类对象,或者把局部变量作为入参,传入匿名函数。

2、线程传参

线程在启动的时候也可以传递参数,如上例中:

class Task
{
public:
	void Working(const string& name){ printf("%s working", name); };
};
Task t;
thread thd = thread(&Task::Working, t, "blwinner");
3、线程停止

线程的停止有两种方式。

方式一:调用thread类成员函数join

join是同步阻塞调用,调用该函数后当前进程被阻塞,直到调用join的线程对象的线程函数执行完毕。

joinable用于判断当前线程是否可以停止。如:

class Task
{
public:
	void Working(){ printf("i'm working"); };
};
Task t;
thread thd = thread(&Task::Working, t);
if(thd.joinable()) thd.join();
方式二:自动停止

当启动线程时,调用detach,则线程会被解绑,自行运行到结束。如:

class Task
{
public:
	void Working(){ printf("i'm working"); };
};
Task t;
thread(&Task::Working, t).detach();
需要注意的是,一个线程对象,要么被赋予了一个线程实例保存,要么被detach后自行运行结束,如果只是创建线程对象而未保存,或者detach,则线程运行结束后引发错误。如果一个线程被一个线程实例保存,则线程实例的生命周期一定要比线程本身长,否则线程对象在线程运行结束前释放的话,也会引发错误。而如果线程实例保存了线程对象,则实例可以通过以上两种方法来退出线程。

4、线程赋值

线程是不允许拷贝构造的(拷贝构造函数被delete),但是允许赋值操作符“=”,赋值操作符的参数是右值引用。线程对象可以被交换,即通过move(右值引用构造函数)或者std::swap或者thread成员函数swap,所以交换以后,源线程实例就不再持有线程对象,目的线程实例持有线程对象,可以停止线程。所以如果你的类里面有一个thread对象,那么你的类将不能拷贝或普通赋值,解决办法是把用到类对象的时候,把类对象用shared_ptr封装,或者把thread对象用shared_ptr封装(类似的还有mutex对象、条件变量)。

5、辅助函数

在thread类内和std命名空间,提供了几个方便的赋值函数。

1)类内:join,joinable,detach,swap,前文已经介绍过。

还有get_id,获取当前线程ID。hardware_concurrency,获取当前系统进程数,如果系统不支持该查询,可能返回0。

2)类外:this_thread命名空间:函数sleep_for,当前线程睡眠若干时间,可用于异步操作时周期查询,参数必须是chrono对象,如:
this_thread::sleep_for(chrono::seconds(1000));
在Windows平台,线程睡眠的最短时间(睡眠精度)不是1ms而是16ms,这和winAPI中sleep的

睡眠最短时间(睡眠精度)相同。
函数sleep_util,当前线程睡眠到某个时间点,可以用于定时执行,参数必须是chrono的

time_point对象。
yield,当前线程暂停,释放内核资源(如CPU时间片)供其他线程执行一段时间。和sleep不同

之处在于,sleep不会释放内核资源。yield的具体执
行过程,依赖于操作系统的实现。在Linux中,yield后的线程会被加入到同等优先级的线程队列

末尾,如果队列中只有当前一个线程,则yield不会生效。
以上就是C++11中线程库的使用办法和注意事项,后面有时间再补充线程同步的辅助类,

包括mutex相关,条件变量相关,等等。



猜你喜欢

转载自blog.csdn.net/blwinner/article/details/54298171