管理线程之创建线程
2016-11-28 00:06
190 查看
基本的线程管理包括:1、创建线程。2、等待它结束或在后台运行。3、向线程函数传递参数,更改线程对象所有权。4、选择线程和使用特定线程。
std::thread object:
[cpp] view
plain copy
void do_some_work();
std::thread my_thread(do_some_work);
这是最简单的情况,std::thread还可以使用可调用类型(callable type),可以在创建线程对象时,在调用函数处传递一个对象的实例。
[cpp] view
plain copy
class background_task
{
public:
void operator()()const
{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f);
在这种情况下,函数对象在新创建线程时被拷贝到新建线程的内存中,并在这开始调用。必须确保拷贝行为等同于原始对象,否则得不到预期的结果。
在向线程构造函数传递函数对象时要避免被称之为”C++最令人烦的语法“。如果你传递一个临时对象代替命名变量,语法上和一个函数的声明一模一样,这时编译器会把它当做函数声明,而不会解释成对象定义。例如:
[cpp] view
plain copy
std::thread my_thread(background_task());
声明了一个函数my_thread,这个函数有一个参数(指向函数的指针,返回background_task对象),返回一个std::thread对象,而不是创建新线程。这时可以通过3种方法来避免:1像上面那样命名函数对象。2使用额外的圆括号。3、使用新的标准初始化语法。
第2中方法
[cpp] view
plain copy
std::thread my_thread((background_task()));
加上额外的圆括号后,会阻止编译器把它解释成函数的声明,这样就能把my_thread解释成定义std::thread类型对象了。
第3中方法
[cpp] view
plain copy
std::thread my_thread{ background_task() };
使用新的标准初始化语法,用花括号而不是圆括号。
一种类型:可调用对象(callable object)可以避免上面的问题是使用拉姆达表达式(lambda expression)。这是C++11的新特性,它允许使用拒不函数,捕捉局部变量,避免传入参数。上面例子可以写成
[cpp] view
plain copy
std::thread my_thread([](
do_something();
do_something_else();
});
一旦你开始你的线程,你必须显示的决定是否等待它结束(调用join)或者让它独立允许(调用detach)。如果不在线程对象销毁之前决定,你的线程会终止(std:thread的析构函数调用std::terminate())。确保你的线程正确的终止或分离,即使在有异常的情况发送,这非常重要。注意,你要在线程对象析构前决定--在你做决定时线程可能早已终止或分离,如果你分离它,在线程对象析构后它还可能运行。
如果你不等待线程结束,那么要确保线程用到的数据一直有效直到线程结束。这不是什么新问题,即使单线程的代码页有这样的问题。(在《Linux多线程服务器编程》中,有讲到过这个问题,使用智能指针)。
一个情形就是函数使用了局部对象的指针或引用,当函数终止时线程并没有终止。下面看这个例子:
[cpp] view
plain copy
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();
}
在这个例子中,新线程在oops函数终止后可能还会运行(分离了新线程),如果线程还在运行,那么函数do_something(i)会使用已经销毁的变量。
一个常见的避免上面问题的方法是使线程函数独立,拷贝这些数据到线程而不是共享。如果使用可调用类型对象(callable object),那么把对象复制到对象里,原始对象立即销毁。但是,此时你还要注意对象是否包含指针或引用。在线程函数内使用局部变量是个坏注意,除非你能确保函数一直存在知道线程执行结束(在函数内调用join等待线程)。
创建线程
线程在创建线程对象时开始运行,创建线程对象使用std::thread。像上节最后那个函数一样,最简单的情况是线程对象创建后运行一个无返回值、无参数的函数。这个函数在线程中运行直到返回,这时线程终止。想一下最复杂的情况,线程运行的函数可能是个函数对象,需要传递参数,执行一系列独立的操作,这些操作需要系统的一些信息,直到接收到某个信号终止。线程执行什么操作或者从哪里开始并不重要,开始一个线程的方法归根到底就是创建线程对象std::thread object:
[cpp] view
plain copy
void do_some_work();
std::thread my_thread(do_some_work);
这是最简单的情况,std::thread还可以使用可调用类型(callable type),可以在创建线程对象时,在调用函数处传递一个对象的实例。
[cpp] view
plain copy
class background_task
{
public:
void operator()()const
{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f);
在这种情况下,函数对象在新创建线程时被拷贝到新建线程的内存中,并在这开始调用。必须确保拷贝行为等同于原始对象,否则得不到预期的结果。
在向线程构造函数传递函数对象时要避免被称之为”C++最令人烦的语法“。如果你传递一个临时对象代替命名变量,语法上和一个函数的声明一模一样,这时编译器会把它当做函数声明,而不会解释成对象定义。例如:
[cpp] view
plain copy
std::thread my_thread(background_task());
声明了一个函数my_thread,这个函数有一个参数(指向函数的指针,返回background_task对象),返回一个std::thread对象,而不是创建新线程。这时可以通过3种方法来避免:1像上面那样命名函数对象。2使用额外的圆括号。3、使用新的标准初始化语法。
第2中方法
[cpp] view
plain copy
std::thread my_thread((background_task()));
加上额外的圆括号后,会阻止编译器把它解释成函数的声明,这样就能把my_thread解释成定义std::thread类型对象了。
第3中方法
[cpp] view
plain copy
std::thread my_thread{ background_task() };
使用新的标准初始化语法,用花括号而不是圆括号。
一种类型:可调用对象(callable object)可以避免上面的问题是使用拉姆达表达式(lambda expression)。这是C++11的新特性,它允许使用拒不函数,捕捉局部变量,避免传入参数。上面例子可以写成
[cpp] view
plain copy
std::thread my_thread([](
do_something();
do_something_else();
});
一旦你开始你的线程,你必须显示的决定是否等待它结束(调用join)或者让它独立允许(调用detach)。如果不在线程对象销毁之前决定,你的线程会终止(std:thread的析构函数调用std::terminate())。确保你的线程正确的终止或分离,即使在有异常的情况发送,这非常重要。注意,你要在线程对象析构前决定--在你做决定时线程可能早已终止或分离,如果你分离它,在线程对象析构后它还可能运行。
如果你不等待线程结束,那么要确保线程用到的数据一直有效直到线程结束。这不是什么新问题,即使单线程的代码页有这样的问题。(在《Linux多线程服务器编程》中,有讲到过这个问题,使用智能指针)。
一个情形就是函数使用了局部对象的指针或引用,当函数终止时线程并没有终止。下面看这个例子:
[cpp] view
plain copy
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();
}
在这个例子中,新线程在oops函数终止后可能还会运行(分离了新线程),如果线程还在运行,那么函数do_something(i)会使用已经销毁的变量。
一个常见的避免上面问题的方法是使线程函数独立,拷贝这些数据到线程而不是共享。如果使用可调用类型对象(callable object),那么把对象复制到对象里,原始对象立即销毁。但是,此时你还要注意对象是否包含指针或引用。在线程函数内使用局部变量是个坏注意,除非你能确保函数一直存在知道线程执行结束(在函数内调用join等待线程)。
相关文章推荐
- Window创建和管理线程
- ACE线程管理机制-线程的创建与管理
- Java:使用Executors创建和管理线程
- java线程的管理和创建
- ACE线程管理机制-线程的创建与管理
- ACE线程管理机制-线程的创建与管理
- Java 线程第三版 第一章Thread导论、 第二章Thread的创建与管理读书笔记
- Java:使用Executors创建和管理线程
- Java:使用Executors创建和管理线程
- Java并发编程之线程管理(线程创建1)
- ACE线程管理机制-线程的创建与管理
- ACE线程管理机制-线程的创建与管理
- 使用Executors创建和管理线程
- Java:使用Executors创建和管理线程
- 线程的创建、管理 与 使用信号灯、互斥量、临界区、事件进行线程同步或互斥
- Java:使用Executors创建和管理线程
- ACE线程管理机制-线程的创建与管理
- Java:使用Executors创建和管理线程
- Java:使用Executors创建和管理线程
- C++ Linux 多线程之创建、管理线程