并发与多线程基础之线程管理

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

猜你喜欢

转载自blog.csdn.net/wymtqq/article/details/80038955