构造
构造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::timeout 和std::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
对象,用户可以通过这个future
的get
方法获取最终的结果。
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线程结合一起使用,使函数对象异步执行。
类似于funciton
, packaged_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
对象,用户可以通过这个future
的get
方法获取最终的结果。
延伸: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;
}