C++并发编程实战笔记-部分函数

本文介绍了C++并发编程中的一些关键函数,包括std::thread::hardware_concurrency()用于获取硬件线程数,std::lock_guard和std::unique_lock用于线程安全的加锁解锁,std::once_flag及call_once确保函数仅执行一次,以及future、async和promise在异步编程中的应用。lock_guard提供简单的线程安全,而unique_lock则更灵活,能应对复杂场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我看这本书真的是云里雾里的,尤其是中文版,感觉真的是,一言难尽,建议可以去看看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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值