В недавно вышедшем выступлении Герба Саттера посвященном многопоточности он привел интересный пример примитива для синхронного выполнения последовательностей операций. Сам Майерс окрестил детище “монитором”, по аналогии с мониторами из мира Java и C#, хотя на мой взгляд, сходства между ними не так уж и много. Суть задумки в том, что бы синхронизировать работу с каким-либо объектом, который изначально не поддерживает синхронизации. При этом, обеспечив синхронность не только на уровне одной операции, но и на уровне “трансзакции”.
vector<future<void>> v;
for(int i=0; i<5; ++i)
{
v.push_back(async([&,i]{
{
// необходимо сделать атомарно.
s += to_string(i) + " " + to_string(i);
s += "\n";
}
{
// так же необходимо сделать атомарно.
cout << s;
}
}));
}
В принципе, в приведенном выше примере вполне можно воспользоваться комбинацией mutex + lock_guard, но так не интересно, не красиво, да и о чем было бы рассказывать в течении полутора часов?
Решение предложенно действительно элегантное:
class Monitor
{
public:
mutable T t_;
mutable mutex m_;
public:
Monitor(T t = T{}) : t_(t) {}
template<typename F>
auto operator()(F f) const -> decltype(f(t_))
{
lock_guard<mutex>_{m_};
return f(t_);
}
};
Что позволяет упростить запись до следущей:
Monitor<string&> m{s};
vector<future<void>> v;
for(int i=0; i<5; ++i)
{
v.push_back(async([&,i]{
m([=](string &s) {
s += to_string(i) + " " + to_string(i);
s += "\n";
});
m([](string &s){
cout << s;
});
}));
}
Не правда ли элегантное решение? Разве что его обоснование сочетанию mutable + const в operator() смущают.
монитор – это классический механизм синхронизации ещё 70-х годов