Bootstrap

c++11 thread

构造

构造std::thread对象时,如果不带参则会创建一个空的thread对象,但底层线程并没有真正被创建,一般可将其他std::thread对象通过move移入其中;如

 std::thread t3(f2, std::ref(n)); // pass by reference
 std::thread t4(std::move(t3)); // t4 is now running f2(). t3 is no longer a thread

如果带参则会创建新线程,而且会被立即运行。包括函数指针、函数对象、lambda等,能让使用者对其进行函数调用操作

如 :explicit thread(_Fn&& _Fx, _Args&&... _Ax)。

std::thread thread1(funct1,this);

class Func
{
public:
    void operator() () const
    {
        do_something();
    }
};

std::thread myThread((Func()));
std::thread myThread([]{
    do_something();
});

std::thread对象不能被复制和赋值,只能被移动。 

析构

std::thread对象析构时,会先判断是否可joinable(),如果可联结,则程序会直接被终止出错。这意味着创建thread对象以后,要在随后的某个地方调用join或detach以便让std::thread处于不可联结状态。

 std::thread t2(f1, n + 1); // pass by value
 t2.join();

联结

joinable():用于判断std::thread对象联结状态,一个std::thread对象只可能处于可联结或不可联结两种状态之一。

      a. 可联结:当线程己运行或可运行、或处于阻塞时是可联结的。注意,如果某个底层线程已经执行完任务,但是没有被join的话,仍然处于joinable状态。

         即std::thread对象(对象由父线程所有)与底层线程保持着关联时,为joinable状态。

      b. 不可联结:

     ① 当不带参构造的std::thread对象为不可联结,因为底层线程还没创建。

     ② 己移动的std::thread对象为不可联结。

     ③ 己调用join或detach的对象为不可联结状态。因为调用join()以后,底层线程己结束,而detach()会把std::thread对象和对应的底层线程之间的连接断开。

join():等待子线程,调用线程处于阻塞模式。join()执行完成之后,底层线程id被设置为0,即joinable()变为false。join() 仅能调用一次;只要 std::thread 对象曾经调用过 join(),线程就不再可汇合(joinable)

 detach():分离子线程,与当前线程的连接被断开,子线程成为后台线程,被C++运行时库接管。则   *this 不再代表任何的线程执行实例。 joinable() = false     get_id() = std::thread::id()

用这函数,主要传给子线程的参数不能是当前线程的局部变量,不然生命周期结束了,还在子线程使用,会崩溃。

函数

get_id:获取线程ID,返回一个类型为std::thread::id的对象。

swap:交换两个线程对象所代表的底层句柄。

std::this_thread::sleep_until: 线程休眠至某个指定的时刻(time point),该线程才被重新唤醒。

std::this_thread::sleep_for: 线程休眠某个指定的时间片(time span),该线程才被重新唤醒,不过由于线程调度等原因,实际休眠时间可能比 sleep_duration 所表示的时间片更长

  std::chrono::milliseconds dura( 2000 );
  std::this_thread::sleep_for( dura );

c++11线程池的基础

std::mutex互斥量

lock、unlock

std::lock_guard    RAII(资源获取即初始化)。

std::mutex mt;

std::lock_guard<std::mutex> lock(mt);

std::unique_lock 

RAII(资源获取即初始化)。std::unique_lock 占用更多的空间,也比 std::lock_guard 略慢。但 std::unique_lock 对象可以不占有关联的互斥,具备这份灵活性需要付出代价:需要存储并且更新互斥信息。

std::mutex mt;

std::unique_lock<std::mutex> lock(mt);

条件变量std::condition_variable

std::condition_variable condit;

wait函数

1.只有独占锁一个参数变量,void wait( std::unique_lockstd::mutex& lock );

wait()将解锁互斥量,并阻塞到本行,阻塞到其他某个线程调用notify_one()成员函数为止。然后wait函数返回,互斥量上锁

2.带独占锁参数和参数lambda表达式

a.如果第二个参数的lambda表达式返回值是false,那么wait()将解锁互斥量,并阻塞到本行.

阻塞到其他某个线程调用notify_one()成员函数并且lambda表达式为true. wait才能返回

b.如果第二个参数的lambda表达式返回值是true,那么wait()直接返回并继续执行

互斥量被上锁,效果就是调了notify_one效果一样

notify_one

不带参数,wait函数返回,互斥量上锁

带参数,需要等lambda表达式为true,wait函数才返回,互斥量上锁

notify_all

和notify_one一样

wait_until、wait_for

可以指定一个时间段,在当前线程收到通知或者指定的时间 rel_time 超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_for 返回。

其返回值有2种状态:timeout和no_timeout 。即std::cv_status::timeoutstd::cv_status::tno_timeout

所以wait_for 函数和windows的等待函数waiforsingleobject函数一样。

DWORD WINAPI datathread(PVOID p)
{
	CMainFrame *pframe = (CMainFrame*)p;
	std::unique_lock<std::mutex> lock((pframe->m_mutex));
	while ((pframe->cond.wait_for(lock, std::chrono::seconds(2))) == 
    std::cv_status::timeout)
	{
		int a = 1;
	}
	return 0;
}

void CMainFrame::OnnewThread()
{
	std::thread thr(datathread, this);
	thr.detach();
}

void CMainFrame::Onclosethread()
{
	std::unique_lock<std::mutex> lock(m_mutex);
	cond.notify_all();
}

std::future

C++11中的std::future是一个模板类。std::future提供了一种用于访问异步操作结果的机制。std::future所引用的共享状态不能与任何其它异步返回的对象共享.

async和packaged_task有一个共同点它们都可以返回一个future对象,用户可以通过这个futureget方法获取最终的结果。

    std::packaged_task<int (int,int)> task(Add);
    future<int> result = task.get_future();
    //启动任务,非异步
    task(4,8);
    cout << "task_thread :" << result.get() << "\n";

. get函数:

(1).当共享状态就绪时,返回存储在共享状态中的值(或抛出异常)。

(2).如果共享状态尚未就绪(即提供者尚未设置其值或异常),则该函数将阻塞调用的线程直到就绪.

wait函数

会让当前线程阻塞,直到子线程返回来

packaged_task

创建thread线程结合一起使用,使函数对象异步执行。

类似于funcitonpackaged_task可以绑定一个可调用对象, 并执行,但是它的返回类型是void,获取它的返回值必须用get_future函数。:

packaged_task类模板也是定义于future头文件中,它包装任何可调用 (Callable) 目标,包括函数、 lambda 表达式、 bind 表达式或其他函数对象,使得能异步调用它,其返回值或所抛异常被存储于能通过 std::future 对象访问的共享状态中。简言之,将一个普通的可调用函数对象转换为异步执行的任务。

常用的函数

1.get_future   

得到std::future 的值

2.reset

    //包装可调用目标时lambda
    packaged_task<int(int,int)> task([](int a, int b){ return a + b;}); 
    //仿函数形式,启动任务,非异步
    task(2, 10);  

    future<int> result = task.get_future();
    //获取共享状态中的值,直到ready才能返回结果或者异常
    cout << "task_lambda :" << result.get() << "\n";


    //包装普通函数
    std::packaged_task<int (int,int)> task(Add);
    future<int> result = task.get_future();

    //启动任务,非异步
    task(4,8);
    cout << "task_thread :" << result.get() << "\n";


	//或者通过线程启动任务,异步启动
	thread td(move(task), 2, 10);
	td.join();
    //获取执行结果
    cout << "task_thread :" << result.get() << "\n";

结论:

1.packaged_task你需要自己调用thread,而async创建后,就会默认执行,后面直接调用get方法就好了。

2.想真正实现异步,需要使用线程启动任务。

async

C++11中的std::async是个模板函数。std::async异步调用函数,在某个时候以Args作为参数(可变长参数)调用Fn,无需等待Fn执行完成就可返回,返回结果是个std::future对象。Fn返回的值可通过std::future对象的get成员函数获取。一旦完成Fn的执行,共享状态将包含Fn返回的值并ready。

std::launch::async  创建新线程执行

默认参数为std::launch::async,创建新线程执行
int cal(int a, int b)
{
	return a + b;
}
std::future<int> result = async(cal,1,2);
result.wait();

launch::deferred   //任务已经创建,线程不创建,不执行函数

std::future<int> result=std::async(launch::deferred,gatewayFunction,para);

cout<<result.get()<<endl;//任务才被执行,调用入口函数,不创建新线程,得到任务计算后的返回值

总结:

async和deferred的不同之处是async强制任务创建新线程执行函数,而deferred不是,所以deferred是在调用处中延迟执行任务。与判断联合使用,可以防止线程过多导致系统崩溃。

async和packaged_task有一个共同点它们都可以返回一个future对象,用户可以通过这个futureget方法获取最终的结果。

延伸:c++11线程池

参考:基于C++11实现线程池 - 知乎

#pragma once
#include <queue>
#include <mutex>
#include <functional>
#include <vector>
#include <thread>
#include <future>


template <typename T>
class SafeQueue
{
private:
	std::queue<T> m_queue; //利用模板函数构造队列

	std::mutex m_mutex; // 访问互斥信号量

public:
	SafeQueue() {}
	SafeQueue(SafeQueue &&other) {}
	~SafeQueue() {}

	bool empty() // 返回队列是否为空
	{
		std::unique_lock<std::mutex> lock(m_mutex); // 互斥信号变量加锁,防止m_queue被改变

		return m_queue.empty();
	}

	int size()
	{
		std::unique_lock<std::mutex> lock(m_mutex); // 互斥信号变量加锁,防止m_queue被改变

		return m_queue.size();
	}

	// 队列添加元素
	void enqueue(T &t)
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		m_queue.emplace(t);
	}

	// 队列取出元素
	bool dequeue(T &t)
	{
		std::unique_lock<std::mutex> lock(m_mutex); // 队列加锁

		if (m_queue.empty())
			return false;
		t = std::move(m_queue.front()); // 取出队首元素,返回队首元素值,并进行右值引用

		m_queue.pop(); // 弹出入队的第一个元素

		return true;
	}
};




class ThreadPool
{
private:
	class ThreadWorker // 内置线程工作类
	{
	private:
		int m_id; // 工作id

		ThreadPool *m_pool; // 所属线程池
	public:
		// 构造函数
		ThreadWorker(ThreadPool *pool, const int id) : m_pool(pool), m_id(id)
		{
		}

		// 重载()操作
		void operator()()
		{
			std::function<void()> func; // 定义基础函数类func

			bool dequeued; // 是否正在取出队列中元素

			while (!m_pool->m_shutdown)
			{
				{
					// 为线程环境加锁,互访问工作线程的休眠和唤醒
					std::unique_lock<std::mutex> lock(m_pool->m_conditional_mutex);
					TRACE(_T("---queue size=%d\n--"), m_pool->m_queue.size());
					// 如果任务队列为空,阻塞当前线程
					if (m_pool->m_queue.empty())
					{
						m_pool->m_conditional_lock.wait(lock); // 等待条件变量通知,开启线程
					}

					// 取出任务队列中的元素
					dequeued = m_pool->m_queue.dequeue(func);
				}

				// 如果成功取出,执行工作函数
				if (dequeued)
					func();
			}
			int a = 1;
		}
	};

	bool m_shutdown; // 线程池是否关闭

	SafeQueue<std::function<void()>> m_queue; // 执行函数安全队列,即任务队列

	std::vector<std::thread> m_threads; // 工作线程队列

	std::mutex m_conditional_mutex; // 线程休眠锁互斥变量

	std::condition_variable m_conditional_lock; // 线程环境锁,可以让线程处于休眠或者唤醒状态

public:
	// 线程池构造函数
	ThreadPool(const int n_threads = 4)
		: m_threads(std::vector<std::thread>(n_threads)), m_shutdown(false)
	{
	}

	ThreadPool(const ThreadPool &) = delete;

	ThreadPool(ThreadPool &&) = delete;

	ThreadPool &operator=(const ThreadPool &) = delete;

	ThreadPool &operator=(ThreadPool &&) = delete;

	// Inits thread pool
	void init()
	{
		for (int i = 0; i < m_threads.size(); ++i)
		{
			m_threads.at(i) = std::thread(ThreadWorker(this, i)); // 分配工作线程
		}
	}
	void wait()
	{
		for (int i = 0; i < m_threads.size(); ++i)
		{
			m_threads.at(i).join(); // 将线程加入到等待队列
		}
	}
	// Waits until threads finish their current task and shutdowns the pool
	void shutdown()
	{
		m_shutdown = true;
		m_conditional_lock.notify_all(); // 通知,唤醒所有工作线程

		for (int i = 0; i < m_threads.size(); ++i)
		{
			if (m_threads.at(i).joinable()) // 判断线程是否在等待
			{
				m_threads.at(i).join(); // 将线程加入到等待队列
			}
		}
	}

	// Submit a function to be executed asynchronously by the pool
	template <typename F, typename... Args>
	auto submit(F &&f, Args &&...args) -> std::future<decltype(f(args...))>
	{
		// Create a function with bounded parameter ready to execute
		std::function<decltype(f(args...))()> func = std::bind(std::forward<F>(f), std::forward<Args>(args)...); // 连接函数和参数定义,特殊函数类型,避免左右值错误

		// Encapsulate it into a shared pointer in order to be able to copy construct
		auto task_ptr = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func);

		// Warp packaged task into void function
		std::function<void()> warpper_func = [task_ptr]()
		{
			(*task_ptr)();
		};

		// 队列通用安全封包函数,并压入安全队列
		m_queue.enqueue(warpper_func);

		// 唤醒一个等待中的线程
		m_conditional_lock.notify_one();

		// 返回先前注册的任务指针
		return task_ptr->get_future();
	}
};







// test.cpp
​
#include <iostream>
#include <random>
#include "thread_pool.h"
std::random_device rd; // 真实随机数产生器
​
std::mt19937 mt(rd()); //生成计算随机数mt
​
std::uniform_int_distribution<int> dist(-1000, 1000); //生成-1000到1000之间的离散均匀分布数
​
auto rnd = std::bind(dist, mt);
​
// 设置线程睡眠时间
void simulate_hard_computation()
{
    std::this_thread::sleep_for(std::chrono::milliseconds(2000 + rnd()));
}
​
// 添加两个数字的简单函数并打印结果
void multiply(const int a, const int b)
{
    simulate_hard_computation();
    const int res = a * b;
    std::cout << a << " * " << b << " = " << res << std::endl;
}
​
// 添加并输出结果
void multiply_output(int &out, const int a, const int b)
{
    simulate_hard_computation();
    out = a * b;
    std::cout << a << " * " << b << " = " << out << std::endl;
}
​
// 结果返回
int multiply_return(const int a, const int b)
{
    simulate_hard_computation();
    const int res = a * b;
    std::cout << a << " * " << b << " = " << res << std::endl;
    return res;
}
​
void example()
{
    // 创建3个线程的线程池
    ThreadPool pool(3);
​
    // 初始化线程池
    pool.init();
​
    // 提交乘法操作,总共30个
    for (int i = 1; i <= 3; ++i)
        for (int j = 1; j <= 10; ++j)
        {
            pool.submit(multiply, i, j);
        }
​
    // 使用ref传递的输出参数提交函数
    int output_ref;
    auto future1 = pool.submit(multiply_output, std::ref(output_ref), 5, 6);
​
    // 等待乘法输出完成
    future1.get();
    std::cout << "Last operation result is equals to " << output_ref << std::endl;
​
    // 使用return参数提交函数
    auto future2 = pool.submit(multiply_return, 5, 3);
​
    // 等待乘法输出完成
    int res = future2.get();
    std::cout << "Last operation result is equals to " << res << std::endl;
​
    // 关闭线程池
    pool.shutdown();
}
​
int main()
{
    example();
​
    return 0;
}

;