Часто какой-то разделяемый несколькими потоками ресурс используется только для чтения. Запись требуется только при инициализации ресурса. Очевидное решение проблемы:
std::shared_ptr<some_resource> resource_ptr;
std::mutex resource_mutex;
void foo() {
std::unique_lock<std::mutex> lk(resource_mutex);
if (!resource_ptr) {
resource_ptr.reset(new some_resource);
}
lk.unlock();
resource_ptr->do_something();
}
Это решение вполне thread-safe. Но оно обладает недостатком: даже после инициализации ресурса, при каждом обращении лочится мьютекс, что, конечно, занимает определенное время (особенно если 2 или более потока пытаются залочить мьютекс одновременно). Есть довольно популярный анти-паттерн, решающий проблему:
std::shared_ptr<some_resource> resource_ptr;
std::mutex resource_mutex;
void foo() {
if (!resource_ptr) { // !!! data race
std::lock_guard<std::mutex> lk(resource_mutex);
if (!resource_ptr) {
resource_ptr.reset(new some_resource);
}
resource_ptr->do_something();
}
resource_ptr->do_something(); // !!! data race
}
Даже от опытных разработчиков со стажем я уже не раз слышал перлы вроде: запись лочить нужно, чтение - нет; бит ведь неделим, либо запишется, либо нет. Бит, может быть, и неделим :) Но вот операции чтения/записи даже одного бита - не атомарны. Это как минимум 3 инструкции на ассемблере: 1) извлечь данные из памяти в регистр; 2) изменить данные; 3) поместить новое значение в память. Между шагами 1-2 и 2-3 может произойти смена контекста. Поэтому в примере выше - race condition.
В С++11, оказывается, есть встроенное решение этой проблемы, обещающее меньший оверхед при локе:
std::shared_ptr<some_resource> resource_ptr;
std::once_flag resource_flag;
void init_resource() {
resource_ptr.reset(new some_resource);
}
void foo() {
std::call_once(resource_flag, init_resource);
resource_ptr->do_something();
}
Более того, в C++11 гарантируется атомарность инициализации локальных статических переменных:
class my_class;
my_class& get_my_class_instance() {
static my_class instance; // thread-safe
return instance;
}
Источник: Anthony Williams, "C++ Concurrency in Action"