C++11多线程-条件变量(std::condition_variable)

    互斥量(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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大锅菜~

谢谢鼓励~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值