Now there is a pen questions like this below.
There are two threads, one thread loop output A, another thread loop output B, how to make these two threads stable output in the console ABABAB….
Without thinking too much, we will certainly define a variable signs isTurnA
, isTurnA
as true
output A, output the same token B, it is one of the most simple finite state machine, just follow the state machine, then certainly agree the answer. isTurnA
Is shared data, so atomic variables or mutex to protect, the mutex used here std::mutex
, the code follows:
class TypeAB
{
public:
TypeAB() : _isTurnA(true) {}
~TypeAB()
{
_threadA.join();
_threadB.join();
}
void PrintAB()
{
_threadA = std::thread(&TypeAB::TypeA, this);
std::this_thread::sleep_for(std::chrono::seconds(2));
_threadB = std::thread(&TypeAB::TypeB, this);
}
private:
std::mutex _mutex;
bool _isTurnA;
std::thread _threadA;
std::thread _threadB;
void TypeB()
{
for (int cnt = 0; cnt < 10; std::this_thread::sleep_for(std::chrono::seconds(3)))
{
std::unique_lock<std::mutex> lock(_mutex);
for (; _isTurnA; )
{
lock.unlock();
lock.lock();
}
std::cout << std::this_thread::get_id() << " - B : " << cnt++ << std::endl;
_isTurnA = true;
}
}
void TypeA()
{
for (int cnt = 0; cnt < 10; std::this_thread::sleep_for(std::chrono::seconds(1)))
{
std::unique_lock<std::mutex> lock(_mutex);
for (; !_isTurnA; )
{
lock.unlock();
lock.lock();
}
std::cout << std::this_thread::get_id() << " - A : " << cnt++ << std::endl;
_isTurnA = false;
}
}
};
This code is running, but look at the following cycle:
for (; !_isTurnA; )
{
lock.unlock();
lock.lock();
}
Continue to unlock the lock, so that another thread has a chance to get a lock, like this do-consuming efficiency. Then we come to a blocking operation:
for (; !_isTurnA; )
{
lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
lock.lock();
}
Like this will bring about a problem, it may just blocking completed and access to the lock, this time, just set up another thread isTurnA
, this situation will cause the current thread blocked more than once. Therefore, blocking this difficult time balancing the blocking time is too short, then spin and there is no difference; blocking time is too long, it will lead to threading issues not timely.
This was back events Event (on the condition variable package) C ++ in that example in the train, condition variables that way, it works as follows:
for (; !_isTurnA; )
{
lock.unlock();
// 线程阻塞于此,等待一个信号,有这个信号就不阻塞了
lock.lock();
}
Specific can see in C ++ event Event (on the condition variable package) introduced on condition variable. Here are standard answer to that question is multi-threaded Road:
#include <bits/stdc++.h>
class TypeAB
{
public:
~TypeAB()
{
_threadA.join();
_threadB.join();
}
void PrintAB()
{
_threadA = std::thread(&TypeAB::TypeA, this);
std::this_thread::sleep_for(std::chrono::seconds(2));
_threadB = std::thread(&TypeAB::TypeB, this);
}
private:
std::mutex _mutex;
std::condition_variable _condi;
bool _isTurnA = true;
std::thread _threadA;
std::thread _threadB;
void TypeB()
{
for (int cnt = 0; cnt < 10; std::this_thread::sleep_for(std::chrono::seconds(3)))
{
std::unique_lock<std::mutex> lock(_mutex);
_condi.wait(lock, [this] () -> bool { return !_isTurnA; });
std::cout << std::this_thread::get_id() << " - B : " << cnt++ << std::endl;
_isTurnA = true;
lock.unlock();
_condi.notify_one();
}
}
void TypeA()
{
for (int cnt = 0; cnt < 10; std::this_thread::sleep_for(std::chrono::seconds(1)))
{
std::unique_lock<std::mutex> lock(_mutex);
_condi.wait(lock, [this] () -> bool { return _isTurnA; });
std::cout << std::this_thread::get_id() << " - A : " << cnt++ << std::endl;
_isTurnA = false;
lock.unlock();
_condi.notify_one();
}
}
};
int main()
{
TypeAB().PrintAB();
return 0;
}
Now take a look at std::condition_variable
the source code section:
// VC14的mutex头文件
class condition_variable
{ // class for waiting for conditions
public:
typedef _Cnd_t native_handle_type;
condition_variable()
{ // construct
_Cnd_init_in_situ(_Mycnd());
}
~condition_variable() _NOEXCEPT
{ // destroy
_Cnd_destroy_in_situ(_Mycnd());
}
condition_variable(const condition_variable &) = delete;
condition_variable &operator=(const condition_variable &) = delete;
void notify_one() _NOEXCEPT
{ // wake up one waiter
_Cnd_signalX(_Mycnd());
}
void notify_all() _NOEXCEPT
{ // wake up all waiters
_Cnd_broadcastX(_Mycnd());
}
void wait(unique_lock<mutex> &_Lck)
{ // wait for signal
_Cnd_waitX(_Mycnd(), _Lck.mutex()->_Mymtx());
}
template <class _Predicate>
void wait(unique_lock<mutex> &_Lck, _Predicate _Pred)
{ // wait for signal and test predicate
while (!_Pred())
wait(_Lck);
}
}
The following code verification questions:
wait
The wording of the second recommendation, can prevent false wake- Condition variables need to match the lock to use, and can only be
std::unique_lock
two reasons,std::unique_lock
there are lock unlock member function works exactly fit condition variable; RAII mode, you can guarantee exception safety - Condition variables can not copy configuration and assignment configuration (two constructors are removed), but may be moved ah (rvalue constructor is not deleted), which also
std::condition_variable
constitutesstd::future
foreshadowed