Параллельное программирование на С++ в действии. Практика разработки многопоточных программ | страница 63



Вот так и с потоками: если несколько потоков ждут одного ресурса (кассира), то, удерживая блокировку дольше необходимого, они заставляют другие потоки проводить в очереди больше времени (не начинайте искать баночку соуса, когда уже подошли к кассе). По возможности захватывайте мьютекс непосредственно перед доступом к разделяемым данным; старайтесь производить обработку данных, не находясь под защитой мьютекса. В частности, не начинайте длительных операций, например файловый ввод/вывод, когда удерживаете мьютекс. Ввод/вывод обычно выполняется в сотни (а то и в тысячи) раз медленнее чтения или записи того же объема данных в памяти. Поэтому если блокировка не нужна для защиты доступа к файлу, то удерживание блокировки заставляет другие потоки ждать без необходимости (так как они не могут захватить мьютекс), и тем самым вы можете свести на нет весь выигрыш от многопоточной работы.

Объект >std::unique_lock отлично приспособлен для таких ситуаций, потому что можно вызвать его метод >unlock(), когда программе не нужен доступ к разделяемым данным, а затем вызвать >lock(), если доступ снова понадобится:

>void get_and_process_data()(1) Во время работы process() зах-

>{                          ←┘ватывать мьютекс не нужно

> std::unique_lock my_lock(the_mutex);

> some_class data_to_process = get_next_data_chunk();

> my_lock.unlock();

> result_type result = process(data_to_process);

> my_lock.lock();                      ←┐Снова захватить мью-

> write_result(data_to_process, result);│текс перед записью

>}                                      (2) результатов

Удерживать мьютекс на время выполнения >process() нет необходимости, поэтому мы вручную освобождаем его перед вызовом (1) и снова захватываем после возврата (2).

Очевидно, что если один мьютекс защищает структуру данных целиком, то не только возрастает конкуренция за него, но и шансов снизить время удержания остается меньше. Поскольку под защитой одного мьютекса приходится выполнять больше операций, то и удерживать его нужно дольше. Такая двойная угроза должна вдвое усилить стремление всюду, где возможно, использовать мелкогранулярные блокировки.

Как следует из примера, выбор подходящей гранулярности определяется не только объемом защищаемых данных, но и временем удержания блокировки и тем, какие операции выполняются под ее защитой. В общем случае блокировку следует удерживать ровно столько времени, сколько необходимо для завершения требуемых операций.