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> 头文件中)也可以获得线程标识。