C++11条件变量condition_variable介绍
1. 使用场景
在C++11中,我们可以使用条件变量(condition_variable)实现多个线程间的同步操作;当条件不满足时,相关线程被一直阻塞,直到某种条件出现,这些线程才会被唤醒。
主要成员函数如下:
函数的使用介绍:
1. std::condition_variable::wait()
(1)wait(unique_lock <mutex>&lck)
当前线程的执行会被阻塞,直到收到 notify 为止。
(2)wait(unique_lock <mutex>&lck,Predicate pred)
当前线程仅在pred=false时阻塞;如果pred=true时,不阻塞。
wait()可依次拆分为三个操作:释放互斥锁、等待在条件变量上、再次获取互斥锁
在线程被阻塞时,该函数会自动调用lck.unlock()释放锁,使得其他被阻塞在锁竞争上的线程得以继续执行。另外,一旦当前线程获得通知(notified,通常是另外某个线程调用 notify_* 唤醒了当前线程),wait() 函数也是自动调用 lck.lock(),使得 lck 的状态和 wait 函数被调用时相同。
2. std::condition_variable::wait_for()
与 std::condition_variable::wait() 类似,不过 wait_for 可以指定一个时间段,在当前线程收到通知或者指定的时间 rel_time 超时之前,该线程都会处于阻塞状态。而一旦超时或者收到了其他线程的通知,wait_for 返回,剩下的处理步骤和 wait() 类似。
if (m_db_cur_conn_cnt >= m_db_max_conn_cnt) { // 如果已经到达了,看看是否需要超时等待 if(timeout_ms <= 0) // 死等,直到有连接可以用 或者 连接池要退出 { log_info("wait ms:%d\n", timeout_ms); m_cond_var.wait(lock, [this] { // log_info("wait:%d, size:%d\n", wait_cout++, m_free_list.size()); // 当前连接数量小于最大连接数量 或者请求释放连接池时退出 return (!m_free_list.empty()) | m_abort_request; }); } else { // return如果返回 false,继续wait(或者超时), 如果返回true退出wait // 1.m_free_list不为空 // 2.超时退出 // 3. m_abort_request被置为true,要释放整个连接池 m_cond_var.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] { // log_info("wait_for:%d, size:%d\n", wait_cout++, m_free_list.size()); return (!m_free_list.empty()) | m_abort_request; }); // 带超时功能时还要判断是否为空 if(m_free_list.empty()) // 如果连接池还是没有空闲则退出 { return NULL; } } if(m_abort_request) { log_warn("have aboort\n"); return NULL; } } else // 还没有到最大连接则创建连接 { CDBConn *pDBConn = new CDBConn(this); //新建连接 int ret = pDBConn->Init(); if (ret) { log_error("Init DBConnecton failed\n\n"); delete pDBConn; return NULL; } else { m_free_list.push_back(pDBConn); m_db_cur_conn_cnt++; // log_info("new db connection: %s, conn_cnt: %d\n", m_pool_name.c_str(), m_db_cur_conn_cnt); } }
3. std::condition_variable::notify_one()
notify_one():没有参数、没有返回值。
解除阻塞当前正在等待此条件的线程之一。如果没有线程在等待,则该函数不执行任何操作。如果超过一个,不会指定具体哪一线程。
std::deque<int> q; std::mutex mu; std::condition_variable cond; void function_1() //生产者 { int count = 10; while (count > 0) { std::unique_lock<std::mutex> locker(mu); q.push_front(count); locker.unlock(); cond.notify_one(); // Notify one waiting thread, if there is one. std::this_thread::sleep_for(std::chrono::seconds(1)); count--; } } void function_2() //消费者 { int data = 0; while (data != 1) { std::unique_lock<std::mutex> locker(mu); while (q.empty()) cond.wait(locker); // Unlock mu and wait to be notified data = q.back(); q.pop_back(); locker.unlock(); std::cout << "t2 got a value from t1: " << data << std::endl; } } int main() { std::thread t1(function_1); std::thread t2(function_2); t1.join(); t2.join(); return 0; }
4.std::condition_variable::notify_all()
唤醒所有的等待(wait)线程。如果当前没有等待线程,则该函数什么也不做。
核心:
①、在消费者里判断队列是否为空后,如果为空则wait,等待生产者发送notify信号。 // 这里可能会出现虚假唤醒的情况,解决办法使将判断队列是否为空改为while循环,当wait被唤醒后,再检测一次。
②、在生产者那里,如果生产了任务,则发送notify信号,告诉消费者可以试图退出wait,判断队列是否为空,如果有任务则调度处理任务,如果还是空则说明此次notify是错误的,可能是其他地方发出来干扰的,生产者继续wait。
③、流程:
软件开启,生成消费者线程消费队列,应该是一个while循环,在循环里获取锁,再来一个while循环判断条件,如果条件成立则wait,wait会自动释放锁;
此时消费者已经没有锁了,在生产者线程里,获取锁,然后往里面加任务,退出作用域释放锁,然后notify告知消费者退出wait,消费者重新获取锁,然后从队列里取任务;
整个过程,生产者加任务时生产者持有锁,消费者取任务时消费者持有锁。
核心:
①、在消费者里判断队列是否为空后,如果为空则wait,等待生产者发送notify信号
②、在生产者那里,如果生产了任务,则发送notify信号,告诉消费者可以试图退出wait,判断队列是否为空,如果有任务则调度处理任务,如果还是空则说明此次notify是错误的,可能是其他地方发出来干扰的,生产者继续wait。
③、流程:
软件开启,生成消费者线程消费队列,应该是一个while循环,在循环里获取锁,再来一个while循环判断条件,如果条件成立则wait,wait会自动释放锁;
此时消费者已经没有锁了,在生产者线程里,获取锁,然后往里面加任务,退出作用域释放锁,然后notify告知消费者退出wait,消费者重新获取锁,然后从队列里取任务;
整个过程,生产者加任务时生产者持有锁,消费者取任务时消费者持有锁。