C++11的新特性之线程类
之前翻译一篇前C++标准委员会的Danny Kalev写的C++11的重大改变
简介
其中提到了新加入的线程类,本篇就谈谈C++11的线程类,关于它的介绍和用法。先看下cppreference.com是怎样定义线程类的,
The class thread
represents a single thread of execution. Threads allow multiple functions to execute concurrently.
线程类代表了一个执行的线程,线程允许多个函数同时执行。
关键看第一句话,也就是一个线程对应一个线程类。第二句话其实没什么新意,因为所有的线程模型都有这个特性,跟C++11的线程类并没有必然的关系。
使用线程类构建的线程在线程对象被创建之后就会立即执行(当然要等操作系统分配CPU时间片),线程入口函数可以通过构造函数参数传入。如果一个异常被抛出或者std::terminate()被调用,那么顶级函数(也就是入口函数)的返回值将被忽略,顶级函数可以通过std::promise()告知它的调用者返回值和异常,或者通过共享变量(例如全局变量)(这需要线程同步,具体请参看std::mutex和std::automic)。
std::thread对象可能不代表任何线程,例如在下面的情况发生,
你调用了detach或者join
或者不和任何线程关联,例如在下面的情况下,
你调用了detach
这里觉得怪怪的,本人没有看出“不代表任何线程“和“不和任何线程关联“之间的区别。官方文档关于这部分的介绍如下,
std::thread
objects may also be in the state that does not represent any thread (after default construction, move from, detach, or join), and a thread of execution may be not associated with any thread
objects (after detach).
没有任何2个std::thread对象同时代表同一个线程,因为std::thread不支持拷贝构造(CopyConstructible
)和赋值操作符(CopyAssignable
),虽然它支持移动拷贝操作(MoveConstructible
)和移动赋值操作(MoveAssignable
)。
创建线程
使用std::thread来创建一个线程,我们使用下面的两种方法,
- 使用函数指针
- 使用类对象
使用函数指针
这种方法和pthread_create方法非常类似,都是先定义一个函数,然后调用pthread_create或者std::thread将这个函数指针传入,线程即被创建,
void doWork( std::string& s )
{
int n = 0;
while( n++ < 5 )
{
std::this_thread::sleep_for( std::chrono::seconds( 1 ));
mm.lock();
num++;
std::cout<<s<<” do something here:”<<num<<std::endl;
mm.unlock();
}
}
int main( void )
{
std::thread t( doWork );
t.join();
}
使用类对象
这种方法是创建一个重载了()操作符的类,然后在创建std::thread的时候,将这个类的实例传入,
class threadBody
{
public:
threadBody()=default;
~threadBody()=default;
void doWork( std::string& s )
{
int n = 0;
while( n++ < 5 )
{
std::this_thread::sleep_for( std::chrono::seconds( 1 ));
mm.lock();
num++;
std::cout<<s<<” do something here:”<<num<<std::endl;
mm.unlock();
}
}
void operator()( std::string s )
{
doWork( s );
}
};
int main( void )
{
threadBody tb;
std::thread t( tb, std::string( “thread 1”));
t.join();
}
等待线程结束
其实在上面的例子中我们已经用到了等待线程结束的方法,就是 join()
它是std::thread对象的实例方法,因此可以直接使用类的实例调用。上面的例子中我们在主线程等待线程t结束。
强制线程结束
std::terminate
这个函数不是用来结束另外一个线程,因为它不接受任何参数,它是线程结束自己的执行,不过现在我还发现使用它的好处,或者说为什么要用它,因为完全可以使用return做到同样的效果。 也许有一个好处就是,和std::terminate对应的有一个terminate_handler,你可以定义这个函数,然后这个函数会被调用。
C++11中没有提供强制结束其他线程的方法,这个原因现在不得而知。如果要强制结束可以先拿到和平台相关的线程id(posix)或者句柄(Windows),然后调用平台的API来结束线程。不过在设计线程的时候,要避免强制结束线程,而是通过线程同步的方法来让通知线程退出它的执行体。
可以关于这个部分下次再花点时间研究一下。
传入参数
可以从上面的例子看到,我们传入了一个参数,那就是std::string,std::thread在创建的时候,允许传入任何个数的参数,只要顶级函数的参数和你传入的参数一致(当然你也可以排除有默认的参数的情况)。下面给个传入多个参数的例子
void do_more_work(int i,std::string s,std::vector<double> v);
std::thread t(do_more_work,42,”hello”,std::vector<double>(23,3.141));
传入的参数都是直接被拷贝的,这点和创建std::thread传入的参数是一样,如果想要线程内部改变这个变量,那么就要使用引用,引用可以使用C++11提供的std::ref
void foo(std::string&);
std::string s;
std::thread t(foo,std::ref(s));
线程同步
在多线程编程中,线程同步永远是一个很重要的话题,要被仔细的设计的部分,否则造成线程无序工作或者死锁之类的问题。
在C++11中,我们可以使用
- std::mutex
- std::automic
来同步线程要共享的资源。上面的例子中,我们已经用到mutex来同步num了。我们会另外再开一篇来介绍这2种方法。
下面是一个完整的例子,
—— main.cpp
#include <iostream>
#include <memory> // —> smart pointer
#include <sstream>
#include <thread>
#include <mutex>
#include <chrono>
#include <unistd.h>
/**************************************************
* test thread
* ************************************************/
int num = 0;MoveAssignable
std::mutex mm;
void doWork( std::string& s )
{
int n = 0;
while( n++ < 5 )
{
std::this_thread::sleep_for( std::chrono::seconds( 1 ));
mm.lock();
num++;
std::cout<<s<<” do something here:”<<num<<std::endl;
mm.unlock();
}
}
class threadBody
{
public:
threadBody()=default;
~threadBody()=default;
void operator()( std::string s )
{
doWork( s );
}
};
void testThread( void )
{
// create thread by function pointer
//std::thread t( doWork );
//t.join();
// create thread by passing object;
threadBody tb;
std::thread t1( tb, std::string( “thread 1”));
std::thread t2( tb, std::string( “thread 2”));
t1.join();
t2.join();
}
int main( int argc, char* argv[] )
{
testThread();
return 0;
}
编译这个文件的Makefile如下
CC=g++
OUTPUT=console
OUTPUTd=console_d
CFLAG=-O3 -std=c++11
CFLAGd=-ggdb -std=c++11
LDFLAGS=-lpthread
SOURCES=main.cpp
all: $(OUTPUT) $(OUTPUTd)
$(OUTPUT):$(SOURCES)
$(CC) $^ $(CFLAG) $(INCLUDE) $(LDPATH) $(LDFLAGS) -o $@
$(OUTPUTd):$(SOURCES)
$(CC) $^ $(CFLAGd) $(INCLUDE) $(LDPATH) $(LDFLAGS) -o $@
.PHONY: clean
clean:
rm -f $(OUTPUT) $(OUTPUTd)
这篇文章写的很粗浅,以后我会话额外的时间,就其中的章节深入的和大家分享一下。
版权所有,禁止转载. 如需转载,请先征得博主的同意,并且表明文章出处,否则
按侵权处理.
One Reply to “C++11的新特性之线程类”