Вы почти наверняка не хотите пытаться сделать каждый класс потокобезопасным, поскольку это может привести к очень неэффективной работе (с множеством ненужных блокировок и разблокировок мьютексов без какой-либо выгоды) и также склонны к взаимоблокировкам (чем больше мьютексов необходимо заблокировать одновременно, тем больше вероятность того, что разные потоки будут блокировать последовательности мьютексов в другом порядке, что является условием входа в тупик и, следовательно, ваша программа зависает от вас) .
То, что вы хотите сделать вместо этого, если выяснить, какие структуры данных должны быть доступны для какого потока (ов). При проектировании ваших структур данных вы хотите попытаться спроектировать их таким образом, чтобы объем данных, разделяемых между потоками, был минимально возможным - если вы можете уменьшить его до нуля, вам не нужно выполнять сериализацию совсем! (вы, вероятно, не справитесь с этим, но если вы сделаете CSP / передачу сообщений , вы можете подойти довольно близко, поскольку единственные мьютексы, которые вам когда-либо понадобятся для блокировки, это те, которые защищают вашу передачу сообщений очереди)
Имейте также в виду, что ваши мьютексы существуют не только для «защиты данных», но и для того, чтобы позволить потоку внести ряд изменений в атом, с точки зрения других потоков, которые могут получить доступ к этим данным. То есть, если ваш поток № 1 должен внести изменения в объекты A, B и C, и каждый из этих трех объектов имеет свой собственный мьютекс, который поток № 1 блокирует перед изменением объекта, а затем разблокирует впоследствии, вы все равно можете иметь условие состязания, потому что поток № 2 может «увидеть» обновление, выполненное наполовину (т. е. поток № 2 может проверить объекты после того, как вы обновили A, но до того, как вы обновили B и C). Поэтому вам обычно нужно поднять свои мьютексы до уровня, на котором они покрывают все объекты, которые вам, возможно, придется изменить за один раз - в примере с ABC это означает, что вам может потребоваться один мьютекс, который используется для сериализации доступа к A, B и C.
Один из подходов к этому - начать с одного глобального мьютекса для всей вашей программы - в любое время любой поток должен прочитать или записать любую структуру данных, которая доступна другим потокам, то есть мьютексу, который он блокирует (и разблокирует впоследствии). Этот дизайн, вероятно, не будет очень эффективным (поскольку потоки могут тратить много времени на ожидание мьютекса), но он определенно не будет страдать от проблем взаимоблокировки. Затем, когда у вас все получится, вы можете посмотреть, действительно ли этот единственный мьютекс является заметным для вас узким местом в производительности - если нет, то все готово, отправьте свою программу :) OTOH, если это узкое место, вы можете проанализировать какие из ваших структур данных логически независимы друг от друга, и разделите ваш глобальный мьютекс на два мьютекса - один для сериализации доступа к подмножеству A структур данных, а другой для сериализации доступа к подмножеству B. (Обратите внимание, что подмножества не не должен быть одинакового размера - подмножество B может содержать только одну конкретную структуру данных, которая имеет решающее значение для производительности) Повторяйте по мере необходимости, пока либо вы не довольны производительностью, либо ваша программа не станет слишком сложной или глючной (в этом случае возможно, вы захотите снова набрать мьютекс-гранулярность, чтобы восстановить ваше здравомыслие).