Безопасность потока - это простая концепция: «безопасно» ли выполнять операцию A в одном потоке, в то время как другой поток выполняет операцию B, которая может совпадать или не совпадать с операцией A. Это может быть расширено для охвата многих потоков,В этом контексте «безопасный» означает:
- Нет неопределенного поведения
- Все потоки гарантированно соблюдают все инварианты структур данных
Фактические операции A и B важны.Если два потока читают обычную переменную int
, то это нормально.Однако, если какой-либо поток может записать в эту переменную, и нет синхронизации, чтобы гарантировать, что чтение и запись не могут происходить вместе, тогда у вас есть гонка данных, которая является неопределенным поведением, и это не потокобезопасен.
Это в равной степени относится и к сценарию, о котором вы спрашивали: если вы не предприняли особые меры предосторожности, то небезопасно читать один поток из структурыв то же время, когда другой поток пишет в него. Если вы можете гарантировать , что потоки не могут получить доступ к структуре данных в одно и то же время с помощью какой-либо формы синхронизации, такой как мьютекс, критическая секция, семафор или событие, тогда проблем нет.
Вы можете использовать такие вещи, как мьютексы и критические секции, чтобы предотвратить одновременный доступ к некоторым данным, так что поток записи является единственным потоком, обращающимся к данным при записи, а поток чтения является единственным потоком, обращающимся кданные, когда он читает, таким образом предоставляя гарантию, которую я только что упомянул.Таким образом, это позволяет избежать неопределенного поведения, упомянутого выше.
Однако вам все равно нужно убедиться, что ваш код безопасен в более широком контексте: если вам нужно изменить более одной переменной, вам нужно удерживать блокировку намьютекс всей операции, а не для каждого отдельного доступа, в противном случае вы можете обнаружить, что инварианты вашей структуры данных могут не соблюдаться другими потоками.
Также возможно, что структура данных может быть поточно-безопасной длянекоторые операции, но не другие.Например, очередь одного производителя с одним потребителем будет в порядке, если один поток выталкивает элементы в очередь, а другой выталкивает элементы из очереди, но прерывается, если два потока выталкивают элементы или два потока выталкивают элементы.
В примере, на который вы ссылаетесь, дело в том, что глобальные переменные неявно разделяются между всеми потоками, и поэтому все обращения должны быть защищены какой-либо формой синхронизации (например, мьютексом), если какой-либо поток может их изменить.С другой стороны, если у вас есть отдельная копия данных для каждого потока, этот поток может изменить свою копию, не беспокоясь о параллельном доступе из любого другого потока, и синхронизация не требуется.Конечно, вам всегда нужна синхронизация, если два или несколько потоков будут работать с одними и теми же данными.
Моя книга, C ++ Параллелизм в действии описывает, что означает, что вещи должны быть безопасными для потоков, как проектировать поточно-ориентированные структуры данных и используемые для этой цели примитивы синхронизации C ++, такие как std::mutex
.