У нас есть класс C ++, который в основном читает и записывает векторы из двоичного файла. Примерная функция чтения, загружающая в память один вектор, выглядит так:

int load (const __int64 index, T* values) const {

 int re = _fseeki64(_file, index * _vectorSize + _offsetData, SEEK_SET); 
 assert(re == 0);

 size_t read = fread(values, sizeof(T), _vectorElements, _file);
 assert(read == _vectorElements);

 return 0;}

Наши программы являются многопоточными с OpenMP, и несколько потоков одновременно обращаются к одному и тому же файлу. Чтобы избежать проблем из-за нескольких потоков, мы всегда закрываем вызов функции в рамках критического оператора OpenMP:

#pragma omp critical {
    load(...);
}

Я знаю, что среда выполнения Microsoft Visual C ++ содержит несколько функций, таких как _fseek_nolock, _fread_nolock, _fwrite_nolock и так далее ... Например, функция _fread_nolock() описывается как

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

Теперь мой вопрос: я понимаю, что функция блокирует «повторные» вызовы, поэтому никакой другой поток не войдет в функцию до того, как другие потоки вернутся. Однако я не понимаю, почему необходимо таким образом защищать одну функцию. IMHO все функции, которые обращаются / изменяют указатель файла (_file в примере кода), должны быть защищены и, следовательно, быть поточно-ориентированными. Это требует создания блокировки вокруг всего функционального блока, который на самом деле вызывает стандартные функции C fseek и fread, поэтому я не вижу смысла предоставлять такие неблокирующие функции.

Может ли кто-нибудь объяснить мне эти механизмы блокировки, потому что я полагаю, что наша параноидальная схема блокировки тратит впустую некоторую производительность?

Заранее спасибо!

0
user229898 11 Дек 2009 в 22:48
Я забыл добавить, что мы говорим о специфичных для Microsoft функциях времени выполнения C ++.
 – 
user229898
11 Дек 2009 в 22:50

3 ответа

Лучший ответ

Для некоторого простого кода достаточно блокировки в ФАЙЛЕ *. Рассмотрим базовую инфраструктуру ведения журнала, в которой вы хотите, чтобы все потоки регистрировались через общий ФАЙЛ *. Внутренняя блокировка гарантирует, что ФАЙЛ * не будет поврежден несколькими потоками, и, поскольку каждая строка журнала должна быть автономной, не имеет значения, как отдельные вызовы чередуются.

2
R Samuel Klatchko 11 Дек 2009 в 23:09
Хорошо, это объясняет, почему fwrite () блокирует другие потоки. AFAIR fwrite () перемещает указатель файла, чтобы несколько потоков могли добавлять сообщения журнала в файл, вызывая только fwrite () снова и снова. Я до сих пор не вижу причин, почему есть _fseek_nolock. Эта функция всегда требует, чтобы вторая функция сообщала указателю файла.
 – 
user229898
11 Дек 2009 в 23:36
Причина, по которой у вас есть _fseek_nolock, заключается в том, что вы заботитесь о производительности и вам нужна атомарная последовательность файловых операций. Вы окружаете последовательность файловых операций _lock_file () / _unlock_file (), чтобы сделать команды атомарными w.r.t. в файл. Как только вы это сделаете, вы можете использовать функции _nolock, чтобы немного уменьшить накладные расходы.
 – 
R Samuel Klatchko
12 Дек 2009 в 01:41

Если вы используете многопоточную среду выполнения C Microsoft, все функции, которым требуются глобальные или статические переменные, будут просто работать должным образом (например, printf и fread, не спрашивайте меня, зачем им глобальные переменные). Однако вы по-прежнему не можете передать структуру FILE * функции, которая записывает в нее, и ожидать, что она будет потокобезопасной.

Таким образом, «потокобезопасные» функции Microsoft являются потокобезопасными только в том смысле, что они реентерабельны, то есть весь доступ к глобальным объектам и статике осуществляется с помощью мьютекса или подобного. Но не в том смысле, что вы можете одновременно вызывать две fprintf () с одним и тем же ФАЙЛОМ *.

Источник: http://msdn.microsoft.com/ en-us / library / 1bh5ewb2% 28VS.71% 29.aspx

1
Thomas Bonini 11 Дек 2009 в 23:03

Если ваше приложение уже гарантирует сериализованный доступ к дескрипторам файлов, вы можете повысить производительность, если укажете среде выполнения c обойти ее собственную сериализацию. Это назначение функций _fread_nolock и т. Д.

0
John Knoeller 23 Дек 2009 в 01:32