Из моих исследований я знаю концепции голода, тупика, справедливости и других проблем параллелизма. Тем не менее, теория отличается от практики в некоторой степени, и реальные инженерные задачи часто включают в себя больше деталей, чем академические бла-бла-бла …
Как разработчик C ++ я некоторое время беспокоился о проблемах с многопоточностью …
Предположим, у вас есть общая переменная x
который относится к некоторой большей части памяти программы. Переменная разделяется между двумя потоками A
а также B
,
Теперь, если мы рассмотрим операции чтения / записи на x
от обоих A
а также B
потоки, возможно, в то же время, есть необходимость синхронизировать эти операции, верно? Так что доступ к x
нужна некоторая форма синхронизации, которая может быть достигнута, например, с помощью мьютексов.
Теперь давайте рассмотрим другой сценарий, где x
изначально написан потоком A
, затем перешли к теме B
(как-то) и эта тема только читает x
, Нить B
затем производит ответ на x
называется y
и передает его обратно в поток A
(опять как-то). Мой вопрос: какие примитивы синхронизации я должен использовать, чтобы сделать этот сценарий потокобезопасным. Я читал об атомарности и, что еще более важно, о заборах памяти — это те инструменты, на которые я должен положиться?
Это не типичный сценарий, в котором есть «критический раздел». Вместо этого некоторые данные передаются между потоками без возможности одновременной записи в одну и ту же область памяти. Таким образом, после записи данные сначала должны быть каким-то образом «сброшены», чтобы другие потоки могли видеть их в действительном и согласованном состоянии перед чтением. Как это называется в литературе, это «видимость»?
Как насчет pthread_once
и его аналог Boost / std, т.е. call_once
, Это помогает, если оба x
а также y
передаются между потоками через своего рода «очередь сообщений», доступ к которой осуществляется с помощью функции «один раз». AFAIK это служит своего рода забором памяти, но я не мог найти никакого подтверждения этому.
А как насчет процессорных кешей и их согласованности? Что я должен знать об этом с инженерной точки зрения? Помогают ли такие знания в вышеупомянутом сценарии или в любом другом сценарии, обычно встречающемся при разработке на C ++?
Я знаю, что я могу смешивать много тем, но я хотел бы лучше понять, какова общая практика проектирования, чтобы я мог повторно использовать уже известные шаблоны.
Этот вопрос в первую очередь относится к ситуации в C ++ 03, так как это моя повседневная среда на работе. Поскольку мой проект в основном включает Linux, я могу использовать только pthreads и Boost, включая Boost.Atomic. Но мне также интересно, изменилось ли что-нибудь, касающееся таких вопросов, с появлением C ++ 11.
Я знаю, что вопрос абстрактный и не такой точный, но любой вклад может быть полезным.
у вас есть общая переменная х
Вот где ты ошибся. Многопоточность НАМНОГО проще, если вы передаете владение рабочими элементами, используя какую-то многопоточную очередь потребитель-производитель, и с точки зрения остальной части программы, включая всю бизнес-логику, ничто не передается.
Передача сообщений также помогает предотвратить коллизии в кеше (поскольку отсутствует истинное совместное использование — за исключением самой очереди производитель-потребитель, и это оказывает тривиальное влияние на производительность, если единица работы велика), а организация данных в сообщения помогает уменьшить ложные обмен).
Параллелизм лучше всего масштабируется, когда вы разделяете проблему на подзадачи. Маленькие подзадачи также гораздо проще рассуждать.
Похоже, вы уже думаете об этом, но нет, примитивы потоков, такие как атомарность, мьютексы и ограждения, не очень хороши для приложений, использующих передачу сообщений. Найдите реальную реализацию очереди (очередь, круговое кольцо, Disruptor, они идут под разными именами, но все отвечают одной и той же потребности). Примитивы будут использоваться внутри реализации очереди, но не кодом приложения.