c++并发编程实战(2)

2.管理线程

2.1 线程基本操作

2.1.1 启动一个线程

2-1

void do_some_work();
std::thread my_thread(do_some_work);

在hello world那个例子中,我们启动了一个线程。这是最简单的一种情况,线程体是一个返回void的无入口参数的函数。下面我们来看一个稍复杂的例子。

2-2

class background_task
{
public:
    void operator()() const
    {
        do_something();
        do_something_else();
    }
};
background_task f;
std::thread my_thread(f);

是的,在这里可以用一个functor来替代简单函数。当然也可以用lambda表达式:

2-3

std::thread my_thread([](
    do_something();
    do_something_else();
});

在线程启动后,如果要等等子线程结束,应该调用join,如果不需要等待则调用detach。在主线程结束时会调用my_thread的析构函数,这样在析构函数中会调用std::terminate将子线程杀死。

除了生命周期问题,在访问局部变量时也要注意。

2-4

struct func
{
    int& i;
    func(int& i_):i(i_){};
    void operator()() {
        for(unsigned j=0;j<1000000;++j)
        {
            do_something(i);   <--1
        }
    }
};
void oops()
{
    int some_local_state=0;
    func my_func(some_local_state);
    std::thread my_thread(my_func);
    my_thread.detach(); <--2
}

局部变量somelocalstate的引用被传入func的成员变量i中。主线程在<--2处detach了子线程,主线程结束后,子线程继续执行,这时语句<--1访问了一个已经被释放的变量。

2.1.2 等待线程结束

正如前边的例子,在thread的实例上调用join()会使调用者等待线程结束。joinable()会返回一个bool,join()和detach()都须在一个joinable的线程实例上调用。同样调用这两个函数之后joinable()会返回false。所以join()和detach()都只能调用一次。

2.1.3 处理异常情况

通常detach会在线程启动后立即调用,但调用join()之前主线程可能会做一些别的事情。如果在调用join()之前主线程抛出异常,子线程可能会出现2-4中的问题。所以我们需要处理异常问题

2-5

struct func; 
void f()
{
    int some_local_state=0;
    func my_func(some_local_state);
    std::thread t(my_func);
    try
    {
        do_something_in_current_thread();
    }
    catch(...)
    {
        t.join();   <--1
        throw;
    }
    t.join();   <--2
}

在dosomethingincurrentthread中出现异常时会由<--2来处理,没有异常时会由1来处理。看到这大概java程序员会想念finally了。在此我们可以使用一个名为RAII(Resource Acquisition Is Initialization)的常用方法,也就是平常用的各种guard。

2-6

class thread_guard
{
    std::thread& t;
    public:
    explicit thread_guard(std::thread& t_):t(t_){}
    ~thread_guard() {
        if(t.joinable())    <--1 
        {
            t.join();       <--2
        }
    }
    thread_guard(thread_guard const&)=delete;   <--3 
    thread_guard& operator=(thread_guard const&)=delete;
};
struct func; 
void f() {
    int some_local_state=0;
    func my_func(some_local_state);
    std::thread t(my_func);
    thread_guard g(t);
    do_something_in_current_thread();
}   <--4

当程序运行到<--4在函数退出前调用thread_guard的析构,从而执行<--2的join()。因为join只能的线程是joinable时调用,所以在<--1处用joinable()进行一下保护。<--3是为了防止错误的调用拷贝构造和赋值操作。这在11x之前是通过写一个private的空函数来实现的。

2.1.4 启动一个后台线程

对一个线程对象调用detach()之后它就成为后台线程了,之后joinable()会返回false。也不能再对它调用join()或detach()了。

2.2 向线程函数传递参数

void f(int i,std::string const& s);
std::thread t(f,3,”hello”);

thread的构造函数支持可变参数,用来向线程函数传递参数,用法和std::bind是一样的。根据需要不同,有时需要向线程传递值,有时需要传引用。例如,在线程内修改变量内容,在主线程去读取,就需要传引用。还有一种情况需要将变量move到线程中,尤其当数据较大时。

2.2.1 传值

错误

void f(int i,std::string const& s);
void oops(int some_param)
{
    char buffer[1024]; 
    sprintf(buffer, "%i",some_param);
    std::thread t(f,3,buffer); 
    t.detach();
}

oops()结束后,buffer已经被释放了,但f()有可能继续访问。

正确

void f(int i,std::string const& s);
void not_oops(int some_param)
{
    char buffer[1024];
    sprintf(buffer,"%i",some_param);
    std::thread t(f,3,std::string(buffer)); <--1 
    t.detach();
}

<--1 在参数传递给thread()构造函数前先构造一个临时对象。

2.2.2 传引用

错误

void update_data_for_widget(widget_id w,widget_data& data); 
void oops_again(widget_id w)
{
    widget_data data;
    std::thread t(update_data_for_widget,w,data); <--1
    display_status();
    t.join();
    process_widget_data(data); <--2
}

因为在<--1处是将data的值传入,所以updatedatafor_widget()中的修改不能返回。修改的方法是std::thread t(update_data_for_widget,w,std::ref(data));

2.2.3 move

在11x中,通过编写右值引用的拷贝构造和赋值操作可以对右值支持move语义。对左值可以用如下方法。

void process_big_object(std::unique_ptr<big_object>);
std::unique_ptr<big_object> p(new big_object);
p->prepare_data(42);
std::thread t(process_big_object,std::move(p));

2.3在thread实例间传递线程所有权

象ifstream和unique_ptr一样,thread是moveable而不是copyable的。

void some_function();
void some_other_function();
std::thread t1(some_function);  <--1 
std::thread t2=std::move(t1);   <--2
t1=std::thread(some_other_function);    <--3 
std::thread t3;     <--4
t3=std::move(t2);   <--5
t1=std::move(t3);   <--6

<--1 创建一个新线程执行函数some_function,并与实例t1关联

<--2 所有权传递给t2,这时t1.get_id()会返回0。

<--3 t1关联到新线程。这里不需要move()因为等号右边是一个右值。在这里t1=t2,t1={t2}都是不可以的,做为一个非copyable的类,thread将基于左值引用的拷贝构造和赋值都delete了。

<--4 thread的缺省构造表示没有关联任何线程

<--5 将线程所有权move给t3

<--6 和<--5的区别是,这时t1不是一个空的实例,会调用std::terminate()将someotherfunction线程终止。

返回一个thread实例

std::thread f()
{
    void some_function();
    return std::thread(some_function); //返回右值
}
std::thread g()
{
    void some_other_function(int);
    std::thread t(some_other_function,42);
    return t;
}

thread做为函数参数

void f(std::thread t);
void g()
{
    void some_function();
    f(std::thread(some_function)); //直接使用右值
    std::thread t(some_function);
    f(std::move(t));    //左值使用move()
}

scope_thread

class scoped_thread
{
    std::thread t;
    public:
    explicit scoped_thread(std::thread t_): 
        t(std::move(t_))
    {
        if(!t.joinable()) 
        throw std::logic_error(“No thread”);
    }
    ~scoped_thread(){
        t.join(); 
    }
    scoped_thread(scoped_thread const&)=delete;
    scoped_thread& operator=(scoped_thread const&)=delete;
};
struct func; 
void f()
{
    int some_local_state;
    scoped_thread t(std::thread(func(some_local_state))); 
    do_something_in_current_thread();
}

2-6的另一种写法。

启动多线程并等待它们结束

void do_work(unsigned id);
void f()
{
    std::vector<std::thread> threads;
    for(unsigned i=0;i<20;++i)
    {
        threads.push_back(std::thread(do_work,i)); 
    }
    std::for_each(threads.begin(),threads.end(),std::mem_fn(&std::thread::join)); 
}

2.4 运行时选择线程数目

std::thread::hardware_concurrency()对同时启动几个线程会给出一个建议的数字,通常是多核机器上core的数目。如i7会返回8。下面给出一个例子

template<typename Iterator,typename T>
struct accumulate_block
{
    void operator()(Iterator first,Iterator last,T& result)
    {
        result=std::accumulate(first,last,result);
    }
};
template<typename Iterator,typename T>
T parallel_accumulate(Iterator first,Iterator last,T init)
{
    unsigned long const length=std::distance(first,last);
    if(!length) 
        return init;
    unsigned long const min_per_thread=25;
    unsigned long const max_threads=(length+min_per_thread-1)/min_per_thread; 
    unsigned long const hardware_threads=std::thread::hardware_concurrency();
    unsigned long const num_threads=std::min(hardware_threads!=0?hardware_threads:2,max_threads);
    unsigned long const block_size=length/num_threads; 
    std::vector<T> results(num_threads);
    std::vector<std::thread> threads(num_threads-1); 
    Iterator block_start=first;
    for(unsigned long i=0;i<(num_threads-1);++i)
    {
        Iterator block_end=block_start;
        std::advance(block_end,block_size); 
        threads[i]=std::thread(accumulate_block<Iterator,T>(),
            block_start,block_end,std::ref(results[i]));
        block_start=block_end; 
    }
    accumulate_block<Iterator,T>()(block_start,last,results[num_threads-1]); 
    std::for_each(threads.begin(),threads.end(),std::mem_fn(&std::thread::join)); 
    return std::accumulate(results.begin(),results.end(),init); 
}

多线程求和函数每个线程至少处理25个对象的累加,线程数目不超过hardware_concurrency()的建议值。

2.5标识线程

有两种方法返回线程id

thread.get_id() 返回和thread实例相关的线程id
this_thread.get_id() 返回当前线程id
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值