我看这本书真的是云里雾里的,尤其是中文版,感觉真的是,一言难尽,建议可以去看看muduo那本书
在这里记录一下,里面常用的出现的并发编程中可能会使用到的函数
一.std::thread::hardware_concurrency() 函数
这个函数可以返回硬件线程所支持的最大上下文的数量
头文件 是
#include < thread>
std::cout << "thread number " << std::thread::hardware_concurrency() << std::endl;
二.std::lock_guard函数和std::unqiue_lock函数
这种方式是以RAII的方式对于线程来进行加锁和解锁,函数析够的时候自动析够,保证线程能够正确被解锁
std::lock——可以一次性锁住多个(两个以上)的互斥量,并且没有副作用(死锁风险)。
std::lock_guard 和 std::unique_lock 两者功能基本相同,unique更为灵活,unique中提供了lock,unlock等函数,可以在等待中重新上锁
template <class Mutex> class lock_guard;
在 lock_guard 对象构造时,传入的 Mutex 对象(即它所管理的 Mutex 对象)会被当前线程锁住。在lock_guard 对象被析构时,它所管理的 Mutex 对象会自动解锁,由于不需要程序员手动调用 lock 和 unlock 对 Mutex 进行上锁和解锁操作,因此这也是最简单安全的上锁和解锁方式,尤其是在程序抛出异常后先前已被上锁的 Mutex 对象可以正确进行解锁操作,极大地简化了程序员编写与 Mutex 相关的异常处理代码。
在lock_guard对象的生命周期内,它所管理的对象对一直保持上锁,随着它的析够,对象将会解锁
-
lock_guard 构造函数如下表所示:
locking (1) explicit lock_guard (mutex_type& m); adopting(2) lock_guard (mutex_type& m, adopt_lock_t tag); copy[deleted](3) lock_guard (const lock_guard&) = delete;
locking 初始化
lock_guard 对象管理 Mutex 对象 m,并在构造时对 m 进行上锁(调用 m.lock())。
adopting初始化
lock_guard 对象管理 Mutex 对象 m,与 locking 初始化(1) 不同的是, Mutex 对象 m 已被当前线程锁住。
void add_to_list(int new_value)
{
std::lock_guard<std::mutex> guard(some_mutex); //线程安全
some_list.push_back(new_value);
}
std::mutex mtx; // mutex for critical section
void print_thread_id (int id) {
mtx.lock();
std::lock_guard<std::mutex> lck(mtx, std::adopt_lock);
std::cout << "thread #" << id << '\n';
}
使用adopt_lock 将mutex的析够由lock_guard来进行管理
那么问题来了,既然已经有了这麽好的lock_guard,为什么还要有unique_lock?
lock_guard的最大缺点也是简单,没有足够的灵活程度,所以在c++11标准库中提供了一个新的unique_lock,它的最大的作用就是方便线程对于互斥量上锁,有更多的灵活方案
unique_lock对象是 管理mutex对象的上锁和解锁操作
unique_lock的构造函数
default (1) unique_lock() noexcept;
新创建的 unique_lock 对象不管理任何 Mutex 对象。
locking (2) explicit unique_lock(mutex_type& m);
新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.lock() 对 Mutex 对象进行上锁,如果此时另外某个 unique_lock 对象已经管理了该 Mutex 对象 m,则当前线程将会被阻塞。
try-locking (3) unique_lock(mutex_type& m, try_to_lock_t tag);
新创建的 unique_lock 对象管理 Mutex 对象 m,并尝试调用 m.try_lock() 对 Mutex 对象进行上锁,但如果上锁不成功,并不会阻塞当前线程。
deferred (4) unique_lock(mutex_type& m, defer_lock_t tag) noexcept;
新创建的 unique_lock 对象管理 Mutex 对象 m,但是在初始化的时候并不锁住 Mutex 对象。 m 应该是一个没有当前线程锁住的 Mutex 对象。
adopting (5) unique_lock(mutex_type& m, adopt_lock_t tag);
新创建的 unique_lock 对象管理 Mutex 对象 m, m 应该是一个已经被当前线程锁住的 Mutex 对象。(并且当前新创建的 unique_lock 对象拥有对锁(Lock)的所有权)。
locking for (6) template <class Rep, class Period> unique_lock(mutex_type& m, const chrono::duration<Rep,Period>& rel_time);
新创建的 unique_lock 对象管理 Mutex 对象 m,并试图通过调用 m.try_lock_for(rel_time) 来锁住 Mutex 对象一段时间(rel_time)。
locking until (7) template <class Clock, class Duration> unique_lock(mutex_type& m, const chrono::time_point<Clock,Duration>& abs_time);
新创建的 unique_lock 对象管理 Mutex 对象m,并试图通过调用 m.try_lock_until(abs_time) 来在某个时间点(abs_time)之前锁住 Mutex 对象。
copy [deleted] (8) unique_lock(const unique_lock&) = delete;
对象不能被拷贝构造。
move (9) unique_lock(unique_lock&& x);
新创建的 unique_lock 对象获得了由 x 所管理的 Mutex 对象的所有权(包括当前 Mutex 的状态)。调用 move 构造之后, x 对象如同通过默认构造函数所创建的,就不再管理任何 Mutex 对象了。
std::unique_lock 主要成员函数
我们来看看 std::unique_lock 的主要成员函数。由于 std::unique_lock 比 std::lock_guard 操作灵活,因此它提供了更多成员函数。具体分类如下:
上锁/解锁操作:lock,try_lock,try_lock_for,try_lock_until 和unlock
修改操作:移动赋值(move assignment),换(swap)(与另一个 std::unique_lock 对象交换它们所管理的 Mutex 对象的所有权),释放(release)(返回指向它所管理的 Mutex 对象的指针,并释放所有权)
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_thread_id (int id) {
std::unique_lock<std::mutex> lck (mtx,std::defer_lock);
// critical section (exclusive access to std::cout signaled by locking lck):
lck.lock();
std::cout << "thread #" << id << '\n';
lck.unlock();
}
int main ()
{
std::thread threads[10];
// spawn 10 threads:
for (int i=0; i<10; ++i)
threads[i] = std::thread(print_thread_id,i+1);
for (auto& th : threads) th.join();
return 0;
}
三.std::once_flag 和call_once函数
对于多线程操作,很多线程都只需要实现一次,c++11中提供了很方便的辅助类once_flag,call_once
首先看一下构造函数:
struct once_flag
{
constexpr once_flag() noexcept;
once_flag(const once_flag&) = delete;
once_flag& operator=(const once_flag&) = delete;
};
template<class Callable, class ...Args>
void call_once(once_flag& flag, Callable&& func, Args&&... args);
可以看出来once_flag不允许修改的
#include < iostream>
#include < thread>
#include < mutex>
std::once_flag flag;
inline void may_throw_function(bool do_throw)
{
// only one instance of this function can be run simultaneously
if (do_throw) {
std::cout << "throw\n"; // this message may be printed from 0 to 3 times
// if function exits via exception, another function selected
throw std::exception();
}
std::cout << "once\n"; // printed exactly once, it's guaranteed that
// there are no messages after it
}
inline void do_once(bool do_throw)
{
try {
std::call_once(flag, may_throw_function, do_throw);
}
catch (...) {
}
}
int main()
{
std::thread t1(do_once, true);
std::thread t2(do_once, true);
std::thread t3(do_once, false);
std::thread t4(do_once, true);
t1.join();
t2.join();
t3.join();
t4.join();
}
值得注意的是once_flag相当于一个锁,使用它的线程都会在上面等待,只有一个线程允许执行。如果该线程抛出异常,那么从等待中的线程中选择一个,重复上面的流程。
四.future和async函数和promise用法
为什么需要future,它在异步编程中经常使用,如果想要一个线程做一些事情,然后自己利用这个时间去做其他的事情,也就是异步编程的基础,future可以获取异步任务的结果,可以把它当成一种简单的线程同步的一种手段
让我们考虑一个简单的事,如果希望获取线程函数的返回结果的时候,我们不能直接通过join()得到结果,必须定义一个变量,线程函数中给这个变量赋值,然后join之后得到结果,这样无疑是及其繁琐。我们可以使用std::async
,它会自动创建一个线程去调用线程函数,返回一个std::future,其中存储线程函数返回的结果。
我们使用线程计算一些值:
std::thread t([ ]) {auto res = sum();});
那么我们怎么可以更加高效一点?如果是计算量很大的函数,那么在运行结果出来之前,我们还可以做点别的
std::future<int> result = std::async( ]( ) { return sum();});
auto c = result.get();
async 可以很方便的获取线程函数的执行结果,会自动创建一个线程去调用线程函数
但是一个future所代表的异步操作不能直接获取,查询future 的状态来获取异步状态
可以查询future_status状态
deferred: 异步操作还没有开始
ready: 异步操作已经OK
timeout : 异步操作已经超时
std::future_status stuas;
future 等待异步操作结束并返回结果,wait只是等待操作完成,没有返回值,wait_for是超时等待返回结果
std::promise 用法
std::promise 的作用就是提供一个不同线程之间的数据同步机制,它可以存储一个某种类型的值传递给future
int main ()
{
std::promise<int> prom; // create promise
std::future<int> fut = prom.get_future(); // engagement with future
std::thread th1 (print_int, std::ref(fut)); // send future to new thread
prom.set_value (10); // fulfill promise
// (synchronizes with getting the future)
th1.join();
return 0;
}
std::packaged_task用法
std::packaged_task的作用就是提供一个不同线程之间的数据同步机制,它可以存储一个函数操作,并将其返回值传递给对应的future, 而这个future在另外一个线程中也可以安全的访问到这个值。
int main ()
{
std::packaged_task<int(int,int)> tsk (countdown); // set up packaged_task
std::future<int> ret = tsk.get_future(); // get future
std::thread th (std::move(tsk),10,0); // spawn thread to count down from 10 to 0
int value = ret.get(); // wait for the task to finish and get result
stdut << "The countdown lasted for " << value << " seconds.\n";
th.join();
return 0;
}