您的位置:首页 > 编程语言 > C语言/C++

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

2013-11-27 20:11 507 查看

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)

a224
{
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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息