C++ 线程、锁、条件变量和异步


C++11开始提供了标准的线程、互斥锁、条件变量和异步等操作,极大的方便了跨平台开发,封装的接口也很友好。

线程

C++11提供了统一的线程封装,在C++11之前POSIX(pthread)和Windows平台的线程实现是不一样的,如果要做跨平台开发需要封装一下。

thread

  • thread支持结构体中的函数调用,函数指针,lambda函数
  • std::thread::joinable:判断线程是否已经结束
  • std::thread::join:等待线程结束
  • std::thread::detach:分离线程,此时的std::thread变得不可用,线程函数会自动执行,执行结束之后会自动释放
#include <thread>
#include <iostream>

class TC {
    public:
        void f(int i) { std::cout << "current thread id " << std::this_thread::get_id() << " parameter value " << i << std::endl; }
};

void f(...) { std::cout << "current thread id " << std::this_thread::get_id() << std::endl; }

int main(int argc, char *argv[])
{
    std::cout << "main thread id " << std::this_thread::get_id() << std::endl;
    std::thread t1 = std::thread([](){
        std::cout << "hello c++ thread, current thread id " << std::this_thread::get_id() << std::endl;
    });
    std::cout << "thread1 id " << t1.get_id() << std::endl;
    t1.detach();

    std::thread t2;
    std::cout << "thread2 joinable " << std::boolalpha << t2.joinable() << " id " << t2.get_id() << std::endl;
    TC tc;
    t2 = std::thread(&TC::f, &tc, 100);
    std::cout << "thread2 joinable " << std::boolalpha << t2.joinable() << " id " << t2.get_id() << std::endl;
    t2.detach();

    std::thread t3 = std::thread(&f, 100, "StoneLiu");
    std::cout << "thread3 joinable " << std::boolalpha << t3.joinable() << " id " << t3.get_id() << std::endl;
    t3.join();
    return 0;
}

输出

main thread id 0x11b189e00
thread1 id 0x70000fb46000
thread2 joinable false id 0x0
thread2 joinable true id 0x70000fbc9000
hello c++ thread, current thread id 0x70000fb46000
thread3 joinable true id 0x70000fc4c000
current thread id 0x70000fbc9000 parameter value 100
current thread id 0x70000fc4c000

设置线程名字

需要在运行之前设置

void SetCurrentThreadName(const char* name) {
#if defined(WEBRTC_WIN)
  struct {
    DWORD dwType;
    LPCSTR szName;
    DWORD dwThreadID;
    DWORD dwFlags;
  } threadname_info = {0x1000, name, static_cast<DWORD>(-1), 0};

  __try {
    ::RaiseException(0x406D1388, 0, sizeof(threadname_info) / sizeof(DWORD), reinterpret_cast<ULONG_PTR*>(&threadname_info));
  } __except (EXCEPTION_EXECUTE_HANDLER) {
  }
#elif defined(WEBRTC_LINUX) || defined(WEBRTC_ANDROID)
  prctl(PR_SET_NAME, reinterpret_cast<unsigned long>(name));
#elif defined(WEBRTC_MAC) || defined(WEBRTC_IOS)
  pthread_setname_np(name);
#endif
}

设置线程优先级

需要在运行之前设置,win32有固定的优先级值,Linux平台因为系统类型比较多,每个平台的范围不同所以需要根据等级手动设置一下。

enum ThreadPriority {
#ifdef WEBRTC_WIN
  kLowPriority = THREAD_PRIORITY_BELOW_NORMAL,
  kNormalPriority = THREAD_PRIORITY_NORMAL,
  kHighPriority = THREAD_PRIORITY_ABOVE_NORMAL,
  kHighestPriority = THREAD_PRIORITY_HIGHEST,
  kRealtimePriority = THREAD_PRIORITY_TIME_CRITICAL
#else
  kLowPriority = 1,
  kNormalPriority = 2,
  kHighPriority = 3,
  kHighestPriority = 4,
  kRealtimePriority = 5
#endif
};

bool SetPriority(ThreadPriority priority) {
#if RTC_DCHECK_IS_ON
  if (run_function_) {
    // The non-deprecated way of how this function gets called, is that it must be called on the worker thread itself.
    RTC_DCHECK(!thread_checker_.CalledOnValidThread());
    RTC_DCHECK(spawned_thread_checker_.CalledOnValidThread());
  } else {
    // In the case of deprecated use of this method, it must be called on the same thread as the PlatformThread object is constructed on.
    RTC_DCHECK(thread_checker_.CalledOnValidThread());
    RTC_DCHECK(IsRunning());
  }
#endif

#if defined(WEBRTC_WIN)
  return SetThreadPriority(thread_, priority) != FALSE;
#elif defined(__native_client__)
  // Setting thread priorities is not supported in NaCl.
  return true;
#elif defined(WEBRTC_CHROMIUM_BUILD) && defined(WEBRTC_LINUX)
  // TODO(tommi): Switch to the same mechanism as Chromium uses for changing
  // thread priorities.
  return true;
#else
#ifdef WEBRTC_THREAD_RR
  const int policy = SCHED_RR;
#else
  const int policy = SCHED_FIFO;
#endif
  const int min_prio = sched_get_priority_min(policy);
  const int max_prio = sched_get_priority_max(policy);
  if (min_prio == -1 || max_prio == -1) {
    return false;
  }

  if (max_prio - min_prio <= 2)
    return false;

  // Convert webrtc priority to system priorities:
  sched_param param;
  const int top_prio = max_prio - 1;
  const int low_prio = min_prio + 1;
  switch (priority) {
    case kLowPriority:
      param.sched_priority = low_prio;
      break;
    case kNormalPriority:
      // The -1 ensures that the kHighPriority is always greater or equal to
      // kNormalPriority.
      param.sched_priority = (low_prio + top_prio - 1) / 2;
      break;
    case kHighPriority:
      param.sched_priority = std::max(top_prio - 2, low_prio);
      break;
    case kHighestPriority:
      param.sched_priority = std::max(top_prio - 1, low_prio);
      break;
    case kRealtimePriority:
      param.sched_priority = top_prio;
      break;
  }
  return pthread_setschedparam(thread_, policy, &param) == 0;
#endif  // defined(WEBRTC_WIN)
}

互斥锁

mutex

互斥锁,以下情况会出现死锁

  • mutex():构造一个互斥锁,不需要传递任何参数
  • lock:获取锁,若锁不可用则阻塞,也就是我们常说的死锁
  • try_lock:尝试获取锁,若锁不可用则直接返回
  • unlock:释放锁
#include <mutex>
#include <iostream>

static std::mutex m_lock;

void f()
{
    std::cout << __func__ << " to lock" << std::endl;
    m_lock.lock();
    std::cout << __func__ << " get lock" << std::endl;
    m_lock.unlock();
    std::cout << __func__ << " release lock" << std::endl;
}

int main(int argc, char *argv[])
{
    m_lock.lock();
    f();
    m_lock.unlock();
    return 0;
}

输入

f to lock

timed_mutex

在mutex的基础上扩展了超时功能,有两个接口try_lock_fortry_lock_until

  • try_lock_for:单位是毫秒(ms),超时多少毫秒还没获取到锁则返回
  • try_lock_until:单位是time_point,也就是到XX时间还没获取到锁则直接返回

recursive_mutex

递归互斥锁,同一个线程可以多次获取同一把锁(内部应该需要通过原子计数次数),还是用刚刚互斥锁那个例子,改为递归互斥锁以后就不会出现死锁了。
pthread_mutex通过设置它的属性为PTHREAD_MUTEX_RECURSIVE实现

#include <mutex>
#include <iostream>

static std::recursive_mutex m_lock;

void f()
{
    std::cout << __func__ << " to lock" << std::endl;
    m_lock.lock();
    std::cout << __func__ << " get lock" << std::endl;
    m_lock.unlock();
    std::cout << __func__ << " release lock" << std::endl;
}

int main(int argc, char *argv[])
{
    m_lock.lock();
    f();
    m_lock.unlock();
    return 0;
}

输出

f to lock
f get lock
f release lock

recursive_timed_mutex

在recursive_mutex的基础上扩展了超时功能,有两个接口try_lock_fortry_lock_until

  • try_lock_for:单位是毫秒(ms),超时多少毫秒还没获取到锁则返回
  • try_lock_until:单位是time_point,也就是到XX时间还没获取到锁则直接返回

std:lock

锁定多个互斥锁而不死锁

#include <thread>
#include <iostream>

std::mutex m1, m2;

int main(int argc, char *argv[])
{
    std::cout << "lock m1, m2." << std::endl;
    // 此时m1和m2两把锁都是未上锁状态,所以lock能成功获取他们的锁
    std::lock(m1, m2);
    std::cout << "m1 to get lock." << std::endl;
    // 释放m1锁
    m1.unlock();    
    m1.lock();
    std::cout << "m1 got lock." << std::endl;
    // 这里的m1已经单独上锁了,m2的锁在前面已经被获取了,所以这里会发生阻塞
    std::lock(m1, m2);
    std::cout << "lock m1, m2." << std::endl;
    return 0;
}

输出

lock m1, m2.
m1 to get lock.
m1 got lock

try_lock

尝试锁定多个互斥锁而不死锁,若其中一个无法获取则返回

try_lock_for, try_lock_until

在一定时间内尝试锁定多个互斥锁而不死锁,若其中一个无法获取则返回

lock_guard, unique_lock, shared_lock

lock_guard:为在作用域块期间占有互斥提供便利 RAII 风格机制

std::mutex m;
std::recursive_mutex rm;

void f1()
{
    // 当调用f1函数结束之后l会被自动释放,l释放的时候会调用析构函数自动释放锁
    std::lock_guard<std::mutex> l(m);
}
void f2()
{
    // 当调用f2函数结束之后l会被自动释放,l释放的时候会调用析构函数自动释放锁
    std::lock_guard<std::recursive_mutex> l(rm);
}

unique_lock:使用unique的方式管理锁,同样是在unique_lock的对象被析构的时候自动释放锁(unlock)

#include <mutex>
#include <iostream>

void unlock(std::unique_lock<std::mutex>& m)
{
    m.unlock();
}

int main(int argc, char *argv[])
{
    std::mutex m;
    std::unique_lock<std::mutex> ul(m, std::adopt_lock);
    m.lock();
    unlock(ul);
    std::cout << "to get lock" << std::endl;
    m.lock();
    std::cout << "got lock" << std::endl;
    return 0;
}

shared_lock:类似unique_ptr和shared_ptr的功能,shared_lock就是增加了一个引入计数

defer_lock, try_to_lock, adopt_lock

用于lock_guard、unique_lock及shared_lock指定锁定策略
defer_lock:不获得互斥的所有权
try_to_lock:尝试获得互斥的所有权而不阻塞
adopt_lock:假设调用方线程已拥有互斥的所有权

条件变量

condition_variable

  • std::condition_variable需要搭配一个std::mutex使用(wait方法需要传递一个std::unique_lockstd::mutex)
  • wait会阻塞当前线程至条件变量被通知或虚假唤醒,当条件变量被唤醒时重新判断条件是否满足,如果不满足则依然会被阻塞
  • signal_one唤醒一个等待线程,如果存在条件变量等待的线程优先唤醒此线程,否则随机唤醒一个线程
  • signal_all唤醒所有等待的线程
#include <mutex>
#include <thread>
#include <iostream>

// 对condition_variable进行封装
class Event {
public:
    Event() : m(), cv() {}
    ~Event() = default;

    void Wait()
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk);
    }

    template <class Predicate>
    void Wait(Predicate pred)
    {
        std::unique_lock<std::mutex> lk(m);
        cv.wait(lk, pred);
    }

    void Signal() { cv.notify_one(); }
    void SignalAll() { cv.notify_all(); }

private:
    // std::mutex and std::condition_variable don't support copy constructor and copy assignment operator
    Event(const Event&) = delete;
    void operator=(const Event&) = delete;

private:
    std::mutex m;
    std::condition_variable cv;
};

void Wait(Event* e, bool* condition)
{
    if (!condition) {
        std::cout << std::this_thread::get_id() << " waiting..." << std::endl;
        e->Wait();
    } else {
        e->Wait([=](){
            std::cout << std::this_thread::get_id() << " condition waiting..." << std::endl;
            return *condition;
        });
    }
    std::cout << std::this_thread::get_id() << " get signal." << std::endl;
}

void Signal(Event* e, bool* c)
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "signal_one" << std::endl;
    e->Signal();
    std::this_thread::sleep_for(std::chrono::microseconds(200));
    *c = true;
}

void SignalAll(Event* e)
{
    std::this_thread::sleep_for(std::chrono::seconds(2));
    std::cout << "signal_all" << std::endl;
    e->SignalAll();
}

int main(int argc, char *argv[])
{
    bool c = false;
    Event e;
    std::thread t1(&Wait, &e, nullptr), t2(&Wait, &e, nullptr), t3(&Wait, &e, &c);
    std::thread t4(&Signal, &e, &c), t5(&SignalAll, &e);
    t1.join(); t2.join(); t3.join(); t4.join(); t5.join();
    return 0;
}

可能的输出

0x70000e3c7000 waiting...
0x70000e344000 waiting...
0x70000e44a000 condition waiting...
signal_one // 唤醒一个等待线程
0x70000e44a000 condition waiting... // 条件变量线程被唤醒,重新检查条件,条件不满足重新阻塞
signal_all // 唤醒所有等待线程
0x70000e3c7000 get signal. // 没有任何条件变量的直接被唤醒
0x70000e344000 get signal. // 没有任何条件变量的直接被唤醒
0x70000e44a000 condition waiting... // 条件变量线程被唤醒,重新检查条件,条件满足不再阻塞
0x70000e44a000 get signal.

condition_variable_any

condition_variable_any是condition_variable的泛化,支持泛型锁(std::mutex, std::recursive_mutex, std::timed_mutex等)。

notify_all_at_thread_exit

这个notify的功能如它名字描述的那样,不是在调用的地方调用notify_all,而是在线程退出时(析构)的时候才调用。例子如下:

#include <mutex>
#include <thread>
#include <iostream>
#include <condition_variable>

std::mutex m;
std::condition_variable cv;

void f1(int* r)
{
    std::this_thread::sleep_for(std::chrono::microseconds(100));
    std::cout << __func__ << "(" << std::this_thread::get_id() << ")" << " r = " << *r << std::endl;
    std::unique_lock<std::mutex> lk(m);
    *r = 100;
    std::cout << __func__ << "(" << std::this_thread::get_id() << ")" << " r = " << *r << std::endl;
    std::notify_all_at_thread_exit(cv, std::move(lk));
    std::cout << __func__ << "(" << std::this_thread::get_id() << ")" << " notify_all_at_thread_exit" << std::endl;
    std::this_thread::sleep_for(std::chrono::microseconds(200));
    std::cout << __func__ << "(" << std::this_thread::get_id() << ")" << " r = " << *r << std::endl;
}

void f2(int* r)
{
    std::cout << __func__ << "(" << std::this_thread::get_id() << ")" << " r = " << *r << std::endl;
    std::unique_lock<std::mutex> lk(m);
    cv.wait(lk);
    std::cout << __func__ << "(" << std::this_thread::get_id() << ")" << " r = " << *r << std::endl;
}

int main(int argc, char *argv[])
{
    int r = 0;
    std::thread t1(&f1, &r), t2(&f2, &r);
    t1.detach(); t2.join();
    return 0;
}

输出

f2(0x70000c436000) r = 0 // 因为thread1延迟了,所以thread2比如先运行,然后阻塞在wait的地方
f1(0x70000c3b3000) r = 0 // 进入f1打印
f1(0x70000c3b3000) r = 100 // 修改r的值再打印
f1(0x70000c3b3000) notify_all_at_thread_exit // 已经调用了notify_all_at_thread_exit函数,但是wait的地方还是被阻塞
f1(0x70000c3b3000) r = 100 // 继续执行f1
f2(0x70000c436000) r = 100 // thread1被释放,wait获取到了信号

cv_status

带作用域枚举 std::cv_status 描述定时等待是否因时限返回。
std::cv_status 为 std::condition_variable 和 std::condition_variable_any 的 wait_for 和 wait_until 方法所用。

  • 枚举常量no_timeout:条件变量因 notify_all 、 notify_one 或虚假地被唤醒
  • 枚举常量timeout:条件变量因时限耗尽被唤醒
#include <map>
#include <mutex>
#include <thread>
#include <string>
#include <iostream>
#include <condition_variable>

std::mutex m;
std::condition_variable cv;

void Wait(int64_t ms)
{
    std::map<std::cv_status, const char*> cv_status_str = {
        {std::cv_status::timeout, "timeout"},
        {std::cv_status::no_timeout, "no_timeout"},
    };

    std::unique_lock<std::mutex> lk(m);
    std::cv_status status = cv.wait_for(lk, std::chrono::milliseconds(ms));
    std::cout << "Wait(" << ms << ") -> " << cv_status_str[status] << std::endl;
}

void SignalAll(int64_t ms)
{
    std::this_thread::sleep_for(std::chrono::milliseconds(ms));
    cv.notify_all();
    std::cout << "SignalAll(" << ms << ")" << std::endl;
}

int main(int argc, char *argv[])
{
    std::thread t1(&Wait, 100), t2(&Wait, 1000), t3(&SignalAll, 500);
    t1.join(); t2.join(); t3.join();
    return 0;
}

输出

Wait(100) -> timeout
SignalAll(500)
Wait(1000) -> no_timeout

异步

launch

用于指定std::async的运行策略,有两种策略分别是asyncdeferred

  • std::launch::async:创建一个新的线程去执行,创建以后会立马执行
  • std::launch::deferred:使用当前线程执行,创建以后不会马上执行,需要调用wait它才会被执行

future

类模板std::future用于接收std::async的返回结果,当它是一个std::launch::deferred类型的std::async,我们还需要调用std::futurewait方法去执行这个异步调用。

  • std::future::valid:函数返回值是否可用,未调用过get方法的状态是true,已经调用过的是false,所以在调用get之前应该先判断一下
  • std::future::get:获取异步线程执行的结果,支持任意类型类型
  • std::future::wait:等待异步线程执行结束,会一直阻塞
  • std::future::wait_for:等待异步线程执行结束,最多等xx时间,如果还没结束也直接返回,返回值是std::timeout_duration(枚举类型)
  • std::future::wait_until:等待异步线程执行结束,直到xx时间,如果还没结束也直接返回,返回值是std::timeout_duration(枚举类型)

async

C++11支持两种构造函数如下:

template< class Function, class... Args>
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>> async( Function&& f, Args&&... args );
template< class Function, class... Args >
std::future<std::result_of_t<std::decay_t<Function>(std::decay_t<Args>...)>> async( std::launch policy, Function&& f, Args&&... args );

如果不指定policy默认是std::launch::async
以下例子是为了验证上面对std::launchstd::future的理解,根据打印能看得出。

#include <thread>
#include <future>
#include <iostream>

int f(int i)
{
    std::cout << "[" << i << "] " << std::this_thread::get_id() << std::endl;
    return 100;
}

int main(int argc, char *argv[])
{
    std::cout << "current thread id " << std::this_thread::get_id() << std::endl;
    auto a1 = std::async(&f, 0);
    auto a2 = std::async(std::launch::async, &f, 1);
    auto a3 = std::async(std::launch::deferred, [](int i){
        std::cout << "[" << i << "] " << std::this_thread::get_id() << std::endl;
        return 8;
    }, 2);
    std::async(std::launch::async, [](int i){
        std::cout << "[" << i << "] " << std::this_thread::get_id() << std::endl;
        return "hello StoneLiu.";
    }, 4);
    std::async(std::launch::deferred, &f, 5);
    auto a4 = std::async(std::launch::deferred, [](){});
    std::cout << "a1 valid " << std::boolalpha << a1.valid() <<  ", waiting ..." << std::endl;
    a1.wait();
    std::cout << "a2 valid " << std::boolalpha << a2.valid() <<  ", waiting ..." << std::endl;
    a2.wait_for(std::chrono::microseconds(200));
    std::cout << "a3 valid " << std::boolalpha << a3.valid() <<  ", waiting ..." << std::endl;
    a3.wait();
    std::cout << "a3 result " << a3.get() << ", valid " << std::boolalpha << a3.valid() << std::endl;
    std::cout << "a4 valid " << std::boolalpha << a4.valid() << std::endl;
    return 0;
}

可能的输出

current thread id 0x11bc7ee00
[0] 0x700001f8d000
[1] 0x700002010000
[4] 0x700002093000
a1 valid true, waiting ...
a2 valid true, waiting ...
a3 valid true, waiting ...
[2] 0x11bc7ee00
a3 result 8, valid false
a4 valid true
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值