Как указывало sblundy , если все объекты являются неизменяемыми и доступны только для чтения, вам не нужно беспокоиться о блокировке, однако это означает, что вам, возможно, придется много копировать объекты. Копирование обычно включает в себя malloc, а malloc использует блокировку для синхронизации распределения памяти между потоками, поэтому неизменяемые объекты могут купить вас меньше, чем вы думаете (сам malloc масштабируется довольно плохо, а malloc медленный ; если вы делаете много malloc в критический раздел производительности, не ожидайте хорошей производительности).
Когда вам нужно только обновить простые переменные (например, 32- или 64-разрядные целочисленные или указатели), просто выполнить над ними операции сложения или вычитания или просто поменять значения двух переменных, большинство платформ предлагают для этого «атомарные операции» (далее GCC предлагает это также). Atomic - это не то же самое, что потокобезопасный . Однако Atomic гарантирует, что, если один поток записывает 64-битное значение, например, в область памяти, а другой поток читает из нее, считывающий получает значение до операции записи или после операции записи, но никогда не * 1009. * нарушено значение в промежутке между операцией записи (например, та, в которой первые 32 бита уже являются новыми, последние 32 бита остаются старыми значениями! Это может произойти, если вы не используете атомарный доступ к такой переменной ).
Однако, если у вас есть структура C с 3 значениями, которую нужно обновить, даже если вы обновляете все три с помощью атомарных операций, это три независимые операции, поэтому читатель может увидеть структуру с одним значением, которое уже обновляется, и два не обновляются. Здесь вам понадобится блокировка, если вы должны убедиться, что читатель видит все значения в структуре как старые или новые значения.
Один из способов сделать масштабирование замков намного лучше - использовать R / W замки. Во многих случаях обновления данных происходят довольно редко (операции записи), но доступ к данным очень частый (чтение данных), например, коллекции (хеш-таблицы, деревья). В этом случае R / W-блокировки принесут вам огромный выигрыш в производительности, поскольку многие потоки могут одновременно удерживать блокировку чтения (они не будут блокировать друг друга), и только если один поток хочет блокировки записи, все остальные потоки заблокированы на время выполнения обновления.
Лучший способ избежать проблем с потоками - не делить данные между потоками. Если каждый поток обрабатывает большую часть времени с данными, к которым не имеет доступа ни один другой поток, вам вообще не понадобится блокировка этих данных (также без атомарных операций). Поэтому постарайтесь делиться как можно меньшим количеством данных между потоками. Тогда вам нужен только быстрый способ перемещения данных между потоками, если это действительно необходимо (ITC, Inter Thread Communication). В зависимости от вашей операционной системы, платформы и языка программирования (к сожалению, вы не сказали нам ни одного из них), могут существовать различные мощные методы для ITC.
И, наконец, еще один прием для работы с общими данными, но без какой-либо блокировки, состоит в том, чтобы убедиться, что потоки не обращаются к одним и тем же частям общих данных. Например. если два потока совместно используют массив, но один будет когда-либо иметь доступ только к четным, а другой только к нечетным индексам, вам не нужно блокировать. Или, если оба используют один и тот же блок памяти и один использует только верхнюю половину, а другой только нижний, блокировка не требуется. Хотя не сказано, что это приведет к хорошей производительности; особенно не на многоядерных процессорах. Операции записи одного потока в эти общие данные (работающие на одном ядре) могут вызвать сброс кеша для другого потока (работающего на другом ядре), и эти сбросы кеша часто являются узким местом для многопоточных приложений, работающих на современных многоядерных процессорах.