C++11的新特性之线程类

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::mutexstd::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来创建一个线程,我们使用下面的两种方法,

  1. 使用函数指针
  2. 使用类对象

使用函数指针

这种方法和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中,我们可以使用

  1. std::mutex
  2. 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的新特性之线程类”

留言

你的邮箱是保密的 必填的信息用*表示