1、启动线程、向线程传递参数:
#include<iostream> #include<thread> #include<chrono> using namespace std; //线程函数 void func(int a, int b, int c) { std::this_thread::sleep_for(std::chrono::seconds(3)); cout << a << " " << b << " " << c << endl; } int main() { //启动线程、创建线程对象t1,绑定线程函数为func std::thread t1(func, 1, 2, 3); //输出t1的线程ID std::cout << "ID:" << t1.get_id() << std::endl; //等待t1线程函数执行结束 t1.join(); std::thread t2(func, 2, 3, 4); //后台执行t2的线程函数,并且不会因为main函数结束时,线程函数未执行完而产生异常 t2.detach();//分离线程 cout << "after t2 ,main is runing" << endl; //以lambda表达式作线程函数 std::thread t4([](int a, int b, int c) { //线程休眠5秒 std::this_thread::sleep_for(std::chrono::seconds(5)); cout << a << " " << b << " " << c << endl; }, 4,5,6); t4.join(); //获取CPU的核数 cout << "CPU: " << thread::hardware_concurrency() << endl; //当添加下面注释掉的语句会抛出异常,因为线程对象先于线程函数结束了,应该保证线程对象的生命周期在线程函数执行完时仍然存在. //std::thread t3(func, 3, 4, 5); return 0; }
2、转移线程所有权
A、线程不可以复制但是可以移动.但是线程移动后,线程对象将不再代表任何线程了:,也就是转移了线程所有权。
void some_function(); void some_other_function(); std::thread t1(some_function); // 1 std::thread t2=std::move(t1); // 2 t1所有权转给了t2 t1=std::thread(some_other_function); // 3 std::thread t3; // 4 t3=std::move(t2); // 5 t1=std::move(t3); // 6 赋值操作将使程序崩溃
B、当显式使用 std::move() 创建t2后②,t1的所有权就转移给了t2。之后,t1和执行线程已经没有关联了;执行some_function的函数现在与t2关联。
C、然后,与一个临时 std::thread 对象相关的线程启动了③。为什么不显式调用 std::move() 转移所有权呢?因为,所有者是一个临时对象——移动操作将会隐式的调用。
D、t3使用默认构造方式创建④,与任何执行线程都没有关联。调用 std::move() 将与t2关联线程的所有权转移到t3中⑤。因为t2是一个命名对象,需要显式的调用 std::move() 。移动操作⑤
E、完成后,t1与执行some_other_function的线程相关联,t2与任何线程都无关联,t3与执行some_function的线程相关联。F、最后一个移动操作,将some_function线程的所有权转移⑥给t1。不过,t1已经有了一个关联的线程(执行some_other_function的线程),所以这里系统直接调用 std::terminate() 终止程序继续运行。这样做(不抛出异常, std::terminate() 是noexcept函数)是为了保证与 std::thread 的析构函数的行为一致。2.1.1节中,需要在线程对象被析构前,显式的等待线程完成,或者分离它;进行赋值时也需要满足这些条件(说明:不能通过赋一个新值给 std::thread 对象的方式来"丢弃"一个线程)。
3、、运行时决定线程数量
A、std::thread::hardware_concurrency() 在新版C++标准库中是一个很有用的函数。这个函数将返回能同时并发在一个程序中的线程数量。
B、实现了一个并行版的 std::accumulate 。代码中将整体工作拆分成小任务交给每个线程去做,其中设置最小任务数,是为了避免产生太多的线程。程序可能会在操作数量为0的时候抛出异常。比如, std::thread 构造函数无法启动一个执行线程,就会抛出一个异常。
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) // 1:如果输入为空,就会得到init的值 { return init; } unsigned long const min_per_thread = 25; unsigned long const max_threads = (length+min_per_thread-1)/min_per_thread; // 2 unsigned long const hardware_threads=std::thread::hardware_concurrency(); unsigned long const num_threads= std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads);//3 unsigned long const block_size=length/num_threads; // 4 std::vector<T> results(num_threads); std::vector<std::thread> threads(num_threads-1); // 5 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); // 6 threads[i]=std::thread( // 7 accumulate_block<Iterator,T>(), block_start,block_end,std::ref(results[i])); block_start=block_end; // 8 } accumulate_block<Iterator,T>()( block_start,last,results[num_threads-1]); // 9 std::for_each(threads.begin(),threads.end(), std::mem_fn(&std::thread::join)); // 10 return std::accumulate(results.begin(),results.end(),init); //11 }
(1):如果输入的范围为空①,就会得到init的值
(2):如果范围内多于一个元素时,都需要用范围内元素的总数量除以线程(块)中最小任务数,从而确定启动线程的最大数量②,这样能避免无谓的计算资源的浪费。比如,一台32芯的机器上,只有5个数需要计算,却启动了32个线程。
(3):计算量的最大值和硬件支持线程数中,较小的值为启动线程的数量③。因为上下文频繁的切换
会降低线程的性能,所以你肯定不想启动的线程数多于硬件支持的线程数量。当 std::thread::hardware_concurrency() 返回0,你可以选择一个合适的数作为你的选择;在本例中,我选择了"2"。你也不想在一台单核机器上启动太多的线程,因为这样反而会降低性能,有可能最终让你放弃使用并发。
(4):每个线程中处理的元素数量,是范围中元素的总量除以线程的个数得出的④
(5):确定了线程个数,通过创建一个 std::vector<T> 容器存放中间结果,并为线程创建一个 std::vector<std::thread> 容器⑤。这里需要注意的是,启动的线程数必须比num_threads少1个,因为在启动之前已经有了一个线程(主线程)。
(6):使用简单的循环来启动线程:block_end迭代器指向当前块的末尾⑥,并启动一个新线程为当前块累加结果⑦。当迭代器指向当前块的末尾时,启动下一个块⑧。启动所有线程后,⑨中的线程会处理最终块的结果。对于分配不均,因为知道最终块是哪一个,那么这个块中有多少个元素就无所谓了。当累加最终块的结果后,可以等待 std::for_each ⑩创建线程的完成(如同在清单2.7中做的那样),之后使用 std::accumulate 将所有结果进行累加⑪。
4:线程标识符:线程标识类型是 std::thread::id ,可以通过两种方式进行检索。第一种,可以通过调用 std::thread 对象的成员函数 get_id() 来直接获取。如果 std::thread 对象没有与任何执行线程相关联, get_id() 将返回 std::thread::type 默认构造值,这个值表示“没有线程”。第二种,当前线程中调用 std::this_thread::get_id() (这个函数定义在 <thread> 头文件中)也可以获得线程标识。