《c++ concurrency in action》读书笔记2--线程管理

一、线程的启动

1. 每个c++程序至少有一个线程,是由C++ runtime启动的

2. 在c++11中,通过一个std::thread 对象启动线程。可以向std::thread传递一个函数,或者实现了调用操作符的类实例, 或者一个lambda表达式。

// 方法一
void do_some_work();
std::thread my_thread(do_some_work);

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

// 方法三
std::thread my_thread([](
    do_something();
    do_something_else();
});

// 方法四
class X
{
public:
    void do_lengthy_work();
};
X my_x;
std::thread t(&X::do_lengthy_work,&my_x);

传递给std::thread的函数对象,会被拷贝到线程对象的上下文中,所以需确保拷贝后的对象和源对象一致。

 

注意,当使用临时对象初始化std::thread对象时:

std::thread my_thread(background_task()); // 声明了一个函数,而不是初始化了一个线程对象。

// 正确的方法:
std::thread my_thread(background_task());
std::thread my_thread(background_task{});

 3.  线程启动后,需要显示的决定是等待其执行完成(join) 或者让它自己运行(detach()), 如让线程自己运行,需确保线程访问的数据在结束之前是一直有效的。不然会出现未定义的行为。调用join()函数后,std::thread对象会清理所有存储的变量,所以对一个线程来说,join()函数只能调用一次,可以通过joinable()函数来检查是否可以调用join.

 4. 为了避免程序终止,需要调用join/detach,  对于detach来说,可以在std::thread对象初始化后就调用,而对于join来说,则需要注意需要确保所有退出通路都调用join(), RAII(Resource Acquisition Is Initialization) 原则,则是一个不错的方法。 

class thread_guard
{
    std::thread& t;
public:
    explicit thread_guard(std::thread& t_):
        t(t_)
    {}
    ~thread_guard()
    {
        if (t.joinable())
        {
            t.join(); 
        }
    }
    
    thread_guard(thread_guard const&)=delete;
    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();
}

上面的scoped_thread类的构造函数使用的是引用,而使用引用可能存在一个问题,即std::thread的对象可以在多个地方被析构,而一个std::thread对象只能被move不能被copy,所以这里可以传一个非引用

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();
}

5. 调用detach()后,线程会在背景运行。一个线程对象,一旦是detach状态,就不能通过std::thread对象来管理它,detach后线程的控制权将会转移到c++运行库,c++运行库会确保线程退出时相应的资源被正确的释放。而对于没有运行的std::thread对象,是不能调用detach()和join()的。

6. 一个detach使用的例子

void edit_document(std::string const& filename)
{
    open_document_and_display_gui(filename);
    while(!done_editing())
    {
        user_command cmd=get_user_input();
        if(cmd.type==open_new_document)
        {
            std::string const new_name=get_filename_from_user();
            std::thread t(edit_document,new_name);
            t.detach();
        }
        else 
        {
            process_user_input(cmd);
        }
    } 
}

 二、向线程函数传递参数

1. 向线程函数传递参数是非常简单的,即将额外的参数传递到std::thread的构造函数中。需要注意的是,默认情况下,参数将被拷贝到线程内部的存储中,

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

在上面的例子中,char const* 类型会在线程的上下文中完成向std::string对象的转化。

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,buffer);   这种方法可能存在 not_oops函数先退出,然后 buffer才被转化为std::string的情况,这种情况的行为是未定义的。应该显示转为string
    std::thread t(f,3,std::string(buffer));
t.detach(); }

当希望将参数做为引用传递时,使用std::ref

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,std::ref(data));
    display_status();
    t.join();
    process_widget_data(data);
}  

 2. 对于只能move的对象, 对于临时对象,会自动使用move, 对于非临时对象,则需要显示的调有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));

三、转移线程对象的所有全

1.  std::thread对象只支持move操作,不支持copy操作。

void some_function();
void some_other_function();
std::thread t1(some_function);
std::thread t2=std::move(t1);
t1=std::thread(some_other_function);
std::thread t3;
t3=std::move(t2);
t1=std::move(t3);

// return from a function
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;
}

// pass to a function
void f(std::thread t);
void g()
{
    void some_function();
    f(std::thread(some_function));
    std::thread t(some_function);
    f(std::move(t));
}

 

2. 等待多个线程完成

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::mem_fn -> 生成指向成员指针的包装对象 std::for_each(threads.begin(),threads.end(), std::mem_fn(&std::thread::join)); }

  

 四 运行时选择线程数量

1. std::thread::hardware_concurrency() 返回一个执行程序真正可以并发的数量。(比如,在一个多核系统中,返回CPU的核数)但这只是一个提示,函数的返回值有可能为0。

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); }

 

五 获取线程标示

1. 可以通过 std::this_thread::get_id() 获取线程标示(为std::thread::id对象,如果两个std::thread::id对象相等,则表示同一个线程或者 “not any thread”.

std::thread::id master_thread;
void some_core_part_of_algorithm()
{
    if(std::this_thread::get_id()==master_thread)
    {
        do_master_thread_work();
    }
    do_common_work();
}

  

  

posted @ 2019-04-23 20:06  雨异奇  阅读(323)  评论(0编辑  收藏  举报