一、线程的启动

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

  

  






最新文章

  1. MYsql 数据库密码忘记(Window)
  2. 【PHP】基于ThinkPHP框架搭建OAuth2.0服务
  3. 理解 Python 中的 *args 和 **kwargs
  4. .net中判断该应用程序是否已经启动,防止重复启动,监控程序启动是否正常
  5. mysql 在线修改表结构工具 gh-ost
  6. ModelAndView使用方法
  7. 理解使用static import 机制(转)
  8. MongoDB Java Driver 3.4操作
  9. xml字符串转对象xml文件转对象
  10. 201521123109《java程序设计》第一周学习总结
  11. AngularJS进阶(四十)创建模块、服务
  12. 发布 ASP.NET Core 2.x 应用到 Ubuntu
  13. formdata 上传图片+进度条
  14. 蚂蚁金服ATEC城市峰会上海举行,三大发布迎接金融科技2019
  15. Docker+Nextcloud快速部署个人网盘
  16. Linux(CentOS7)安装Tomcat
  17. Nginx——location常见配置指令,alias、root、proxy_pass
  18. BZOJ2957: 楼房重建(分块)
  19. 前端开发 —— google chart 的使用
  20. jsp数据库连接大全和数据库操作封装到Javabean

热门文章

  1. Centos7下使用Ceph-deploy快速部署Ceph分布式存储-操作记录
  2. 灵雀云受邀加入VMware 创新网络,共同助力企业数字化进程
  3. 【数据结构】算法 LinkList (Merge Two Sorted Lists)
  4. 详解PHP中foreach
  5. John Deere Service Advisor with Nexiq clone 90% Worked
  6. java线程学习之volatile关键字
  7. tomcat服务器1
  8. Thanks David&#39;s Share
  9. VC GDI 像素转厘米(英寸)[转发]
  10. List、Set、Map的区别