Chapter2 Managing threads


2.1 Basic thread management

2.1.1 Launching a thread

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

上面这段代码会引起歧义,由于backgroud_task() 引起。如果未明确指定函数名称,而是由这种暂时的代码代替函数名称,则可能引起歧义,造成my_thread是函数,该函数接收一个函数指针作为参数。解决方案如下:

std::thread my_thread((background_task()));       
std::thread my_thread{background_task()};    

Note that you only have to make this decision before the  std::thread object is destroyed—the thread itself may well have finished long before you join with it or detach it, and if you detach it, then the thread may continue running long after the  std::thread object is destroyed.

If you don’t wait for your thread to finish, then you need to ensure that the data accessed  by  the  thread  is  valid  until  the  thread  has  finished  with  it. 

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

上面这段代码有问题。i这个变量引用了some_local_state,并且my_thread在创建之后detach了,可能造成i成为dangling reference.

2.1.2 Waiting for a thread to complete

If you need to wait for a thread to complete, you can do this by calling join() on the associated std::thread  instance. join()  is  simple  and  brute  force—either  you  wait  for  a  thread  to  finish  or  you

don’t. 

2.1.3 Waiting in exceptional circumstances

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();
        throw;
    }
    t.join();           
}

上面这段代码的确能够解决exception问题,但是没必要,请看如下代码:

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();
} 
One way of doing this is to use the standard Resource Acquisition Is Initialization (RAII) idiom and provide a class that does the  join() in its destructor.

2.1.4 Running threads in the background

Calling detach()   on  a  std::thread  object  leaves  the  thread  to  run  in  the  background, with no direct means of communicating with it. It’s no longer possible to wait for that thread to complete; if a thread becomes detached, it isn’t possible to obtain a std::thread object that references it, so it can no longer be joined. Detached threads truly run in the background; ownership and control are passed over to the C++ Runtime Library, which ensures that the resources associated with the thread are correctly reclaimed when the thread exits.

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

上面这段代码是word中在编辑文件的时候,根据接收用户不同的输入进行处理,如果是新建文件,则启动一个新的线程进行处理。


2.2 Passing arguments to a thread function

void f(int i,std::string const& s);
std::thread t(f,3,”hello”);
传参的基本方法。
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在线程t之前结束,就造成了buffer指向的数据无效了!

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

可以通过上述这种方式进行解决,但是造价个人感觉还是蛮大的。

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

上面这种方式是按值传递,但是我们有的时候想按引用传递,如下的方式是按引用传递:

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

注意使用了std::ref(data).

An  example  of  such  a  type  is std::unique_ptr ,  which  provides  automatic  memory  management  for  dynamically allocated objects. Only one std::unique_ptr  instance can point to a given object at a time, and when that instance is destroyed, the pointed-to object is deleted. The move constructor  and  move  assignment  operator  allow  the  ownership  of  an  object  to  be  transferred around between std::unique_ptr  instances.

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 Transferring ownership of a thread

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

第4行将t1线程的拥有权转移给t2,然后t1重新拥有了对some_other_function的控制。第7行将t2的拥有权转移给了t3,最后一行将t3的拥有权转移给了t1,由于现在t1同时拥有了对两个不同函数的拥有权,因此造成t1终止。

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

能够返回一个线程,并且赋值给其他的线程,完成线程拥有权的转移。

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

上面这段代码通过move进行了线程拥有权的转移!

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(&s

上面这段代码启动一批线程,并且对他们进行集体管理!

2.4 Choosing the number of threads at runtime

One feature of the C++ Standard Library that helps here is  std::thread::hardware_concurrency() . This function returns an indication of the number of threads that can truly run concurrently for a given execution of a program. 

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);   
}
上面这段程序进行求和运算,根据数据范围以及机器能够支持的线程数量,进行创建合适的线程数目。

2.5 Identifying threads
Thread  identifiers  are  of  type  std::thread::id   and  can  be  retrieved  in  two  ways. First,  the  identifier  for  a  thread  can  be  obtained  from  its  associated std::thread object  by  calling  the get_id()   member  function.  If  the  std::thread  object  doesn’t have  an  associated  thread  of  execution,  the  call  to get_id()   returns  a  default-constructed std::thread::id  object, which indicates “not any thread.” Alternatively, the identifier for the current thread can be obtained by calling  std::this_thread::
get_id() , which is also defined in the  <thread>  header.

2.6 Summary

In  this  chapter  I  covered  the  basics  of  thread  management  with  the  C++  Standard Library: starting threads, waiting for them to finish, and not waiting for them to finish because you want them to run in the background. You also saw how to pass arguments into the thread function when a thread is started, how to transfer the responsibility for managing a thread from one part of the code to another, and how groups of threads can be used to divide work. Finally, I discussed identifying threads in order to associate  data  or  behavior  with  specific  threads  that’s  inconvenient  to  associate  through alternative means. Although you can do quite a lot with purely independent threads that each operate on separate data, as in listing 2.8 for example, sometimes it’s desirable to share data among threads while they’re running. Chapter 3 discusses the issues surrounding  sharing  data  directly  among  threads,  while  chapter  4  covers  more  general issues surrounding synchronizing operations with and without shared data.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值