文章目录
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, ¶m) == 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_for
和try_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_for
和try_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
的运行策略,有两种策略分别是async
和deferred
- std::launch::async:创建一个新的线程去执行,创建以后会立马执行
- std::launch::deferred:使用当前线程执行,创建以后不会马上执行,需要调用wait它才会被执行
future
类模板std::future
用于接收std::async
的返回结果,当它是一个std::launch::deferred
类型的std::async
,我们还需要调用std::future
的wait
方法去执行这个异步调用。
- 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::launch
和std::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