互斥量(std::mutex),互斥量是多线程间同时访问某一共享变量时,保证变量可被安全访问的手段。
在多线程编程中,还有另一种十分常见的行为:线程同步。线程同步是指线程间需要按照预定的先后次序顺序进行的行为。C++11对这种行为也提供了有力的支持,这就是条件变量。条件变量位于头文件condition_variable下。
配合std::unique_lock一起使用,主要用于“生产者”、“消费者”处理场景。
1. std::condition_variable
条件变量提供了两类操作:wait和notify。这两类操作构成了多线程同步的基础。
一句话总结:就是线程在执行时,需要等待某个条件或变量为ture时才会接着往下执行。
1.1 wait
wait是线程的等待动作,直到其它线程将其唤醒后,才会继续往下执行。下面通过伪代码来说明其用法:
std::mutex mutex;
std::condition_variable cv;
// 条件变量与临界区有关,用来获取和释放一个锁,因此通常会和mutex联用。
std::unique_lock lock(mutex);
// 1、此处会释放lock,然后在cv上等待,直到其它线程通过cv.notify_xxx来唤醒当前线程
// 2、cv被唤醒后会再次对lock进行上锁,然后wait函数才会返回。
// 3、wait返回后可以安全的使用mutex保护的临界区内的数据。此时mutex仍为上锁状态
cv.wait(lock)
需要注意的一点是, wait有时会在没有任何线程调用notify的情况下返回,这种情况就是有名的spurious wakeup。因此当wait返回时,你需要再次检查wait的前置条件是否满足,如果不满足则需要再次wait。wait提供了重载的版本,用于提供前置检查。
template <typename Predicate>
void wait(unique_lock<mutex> &lock, Predicate pred)
{
while(!pred())
{
wait(lock);
}
}
除wait外, 条件变量还提供了wait_for和wait_until,这两个名称是不是看着有点儿眼熟,std::mutex也提供了_for和_until操作。在C++11多线程编程中,需要等待一段时间的操作,一般情况下都会有xxx_for和xxx_until版本。前者用于等待指定时长,后者用于等待到指定的时间。
1.2 notify
了解了wait,notify就简单多了:唤醒wait在该条件变量上的线程。notify有两个版本:notify_one和notify_all。
- notify_one 唤醒等待的一个线程,注意只唤醒一个。
- notify_all 唤醒所有等待的线程。使用该函数时应避免出现惊群效应。
其使用方式见下例: 直到lock释放了mutex,被唤醒的线程才会从wait返回。
std::mutex mutex;
std::condition_variable cv;
void func()
{
{
std::unique_lock lock(mutex);
//do sth
}
// 所有等待在cv变量上的线程都会被唤醒。但直到lock释放了mutex(所以上面可以将锁放在局部域中),被唤醒的线程才会从wait返回。
cv.notify_all(lock)
}
示例1:
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <condition_variable>
std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// 等待直至 main() 发送数据
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return ready;});
// 等待后,我们占有锁。
std::cout << "Worker thread is processing data\n";
data += " after processing";
// 发送数据回 main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
// 通知前完成手动解锁,以避免等待线程才被唤醒就阻塞(细节见 notify_one )
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// 发送数据到 worker 线程
{
std::lock_guard<std::mutex> lk(m);
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// 等候 worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
输出:
main() signals data ready for processing
Worker thread is processing data
Worker thread signals data processing completed
Back in main(), data = Example data after processing
示例2:
#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>
std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;
void waits()
{
std::unique_lock<std::mutex> lk(cv_m);
std::cout << "Waiting... \n";
cv.wait(lk, []{return i == 1;});
std::cout << "...finished waiting. i == 1\n";
done = true;
}
void signals()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Notifying falsely...\n";
cv.notify_one(); // 等待线程被通知 i == 0.
// cv.wait 唤醒,检查 i ,再回到等待
std::unique_lock<std::mutex> lk(cv_m);
i = 1;
while (!done)
{
std::cout << "Notifying true change...\n";
lk.unlock();
cv.notify_one(); // 等待线程被通知 i == 1 , cv.wait 返回
std::this_thread::sleep_for(std::chrono::seconds(1));
lk.lock();
}
}
int main()
{
std::thread t1(waits), t2(signals);
t1.join();
t2.join();
}
//可能的输出
Waiting...
Notifying falsely...
Notifying true change...
...finished waiting. i == 1
示例3:
class dataMgr : public CSingle<dataMgr>
{
friend class CSingle<dataMgr>;
dataMgr() {};
public:
//添加数据
bool addData(const string& strguid, const string& strdata)
{
{
std::unique_lock<std::mutex> lock(map_mutex);
auto it = mapData.find(strguid);
if (it != mapData.end())
{
assert(0);
mapData[strguid] = strdata;
}
else
{
mapData.insert(std::make_pair(strguid, strdata));
}
}
//condition_.wait_for 退出的前提是lock.unlock(),等lock解锁之后,再notify_all
condition_.notify_all();
return true;
}
//获取数据
bool getData(string& strdata, const string& strguid, int waittime)
{
auto find_func = [&]()
{
auto it = mapData.find(strguid);
if (it != mapData.end())
{
strdata = it->second;
mapData.erase(strdata);
return true;
}
return false;
};
std::unique_lock<std::mutex> lock(map_mutex);
if (find_func())
{
return true;
}
if (waittime > 0)
{
//原子地释放lock ,阻塞当前线程,并将它添加到等待在 *this 上的线程列表。
//线程将在执行 notify_all() 或 notify_one() 时,或度过相对时限 waittime 时被解除阻塞。
//若当前线程未锁定 lock.mutex() ,则调用此函数是未定义行为。
condition_.wait_for(lock, std::chrono::milliseconds(waittime), find_func);
}
return !strdata.empty();
}
private:
map<string, string> mapData;
std::mutex map_mutex; // map锁
std::condition_variable condition_; // 条件变量
};
参考:
1、https://www.jianshu.com/p/a31d4fb5594f (简书)
2、https://zh.cppreference.com/w/cpp/thread/condition_variable (cppreference.com)