2012-03-05 4 views
7

Я продолжаю слышать о потокобезопасности. Что именно так и как и где я могу научиться программировать потокобезопасный код?Программирование потокобезопасности

Также предположим, что у меня есть 2 потока, которые записывают в структуру и другую, которая читает с нее. Это опасно? Есть ли что-нибудь, что я должен искать? Я не думаю, что это проблема. Оба потока не будут (а не могут) получать доступ к структуре в то же самое время.

Кроме того, может кто-нибудь скажет мне, как в этом примере: https://stackoverflow.com/a/5125493/1248779 мы делаем лучшую работу в вопросах параллелизма. Я не понимаю.

+1

Первые несколько хитов на [Google] (https://www.google.com/#hl=ru&sclient=psy-ab&q=c%2B%2B+thread-safe+programming&pbx=1&oq=c%2B%2B + потокобезопасный + программирование и водно = е & АКИ = & акль = & gs_sm = 3 & gs_upl = 532l4457l0l4950l27l12l0l2l2l0l481l3318l0.4.3.4.1l12l0 & gs_l = hp.3 ... 532l4457l0l4950l27l12l0l2l2l0l481l3318l0j4j3j4j1l12l0 & BAV = on.2, or.r_gc.r_pw.r_cp.r_qf., cf.osb & Fp = 3e688b5b28c62a4d & BIW = 1306 & bih = 867) кажутся весьма поучительными. – jogojapan

+1

Извините, я пробовал это, я просто нашел его слишком сложным. Мне нужен простой пример и рабочий стол. Я думаю, – Kam

+0

Почему два потока не могут получить доступ к структуре в одно и то же время? Если вы его не защищаете (например, семафором), вы не можете знать, записывает ли первый поток часть структуры, тогда планировщик ОС решает запустить поток, который читает структуру, которая теперь не будет полностью обновляться. –

ответ

4

резьбы безопасность является одним из аспектов более широкого набора вопросов под общим заголовком «Параллельное программирование». Я бы предложил прочитать эту тему.

Ваше предположение, что два потока не могут получить доступ к структуре в одно и то же время, не является хорошим. Во-первых: сегодня у нас есть многоядерные машины, поэтому два потока могут работать в одно и то же время. Во-вторых: даже на одной основной машине срезы времени, заданные для любой другой нити, являются непредсказуемыми. Вы должны предвидеть, что муравей любое произвольное время, которое может обрабатывать «другой» поток. См. Ниже пример «окна возможностей».

Концепция безопасности потока - это точно ответить на вопрос «это опасно в любом случае». Ключевой вопрос заключается в том, возможно ли, чтобы код, работающий в одном потоке, получал противоречивое представление о некоторых данных, эта несогласованность возникает, поскольку во время запуска другой поток находился в середине изменения данных.

В вашем примере один поток читает структуру и в то же время пишет другую. Предположим, что существуют два смежных областях:

{ foreground: red; background: black } 

и писатель находится в процессе изменения этих

foreground = black; 
      <=== window of opportunity 
    background = red; 

Если читатель считывает значения в только, что окно возможностей, то он видит «нонсенс "сочетание

{ foreground: black; background: black } 

Эта сущность этой модели является то, что в течение короткого времени, в то время как мы делаем изменения, система становится непоследовательной и читатели не должны использовать значение. Как только мы закончим наши изменения, станет безопаснее читать снова.

Следовательно, мы используем API CriticalSection, упомянутые Stefan, чтобы предотвратить поток, наблюдающий несогласованное состояние.

+1

C#? Это помечено как C++. – Pubby

+0

Привет, Пожалуйста, проверьте это http://stackoverflow.com/a/5125493/1248779 Он говорит, что это то, как вы можете сделать материал потокобезопасным. Я не вижу его. Что различается до и после инкапсуляции? – Kam

+1

Это правильно, но может быть и хуже. Я не знаю, какого типа эти цвета, но если они не являются фундаментальным атомным типом, вы фактически получаете _windows of the option_ непосредственно в самом задании. –

1

При написании многопоточных программ на C++ на платформах WIN32 вам необходимо защитить определенные общие объекты, чтобы только один поток мог получить к ним доступ в любой момент времени из разных потоков. Для достижения этой цели вы можете использовать 5 системных функций. Это InitializeCriticalSection, EnterCriticalSection, TryEnterCriticalSection, LeaveCriticalSection и DeleteCriticalSection.

Кроме того, возможно, это ссылки могут помочь: how to make an application thread safe?

http://www.codeproject.com/Articles/1779/Making-your-C-code-thread-safe

0

Безопасная защита потоков, когда определенный блок кода защищен от доступа более чем одним потоком. Это означает, что обрабатываемые данные всегда остаются в постоянном состоянии.

Типичного пример является производителем потребительской проблемы, когда один поток считывает из структуры данных, в то время как другой поток записывает в ту же структуру данных: Detailed explanation

+0

Речь идет не о защите блока кода, а о защите части данных. –

+1

@Alex, ну, это как на самом деле. В тот момент, когда вы включаете устройства (даже консоль, окно или файл), вам все равно понадобятся блокировки, даже если вы не изменяете какие-либо данные в своей собственной программе. –

7

Это очень глубокая тема. В самом сердце нити, как правило, делают все быстрее, используя несколько ядер одновременно; или о выполнении длинных операций в фоновом режиме, когда у вас нет хорошего способа чередования операций с «первичным» потоком. Последнее очень распространено в программировании пользовательского интерфейса.

Ваш сценарий является одним из классических проблемных мест, и один из первых сталкивается. Для меня редко бывает структура, в которой члены действительно независимы. Очень часто требуется изменить несколько значений в структуре, чтобы поддерживать согласованность. Без каких-либо предосторожностей очень возможно изменить первое значение, затем другой поток прочитает структуру и начнет работать до того, как будет записано второе значение.

Простым примером будет структура «point» для 2d-графики. Вы хотите переместить точку с [2,2] до [5,6]. Если бы у вас был другой поток, который рисовал бы линию до этой точки, вы могли бы легко рисовать на [5,2].

Это верхушка айсберга. Есть много замечательных книг, но изучение этого пространства обычно происходит примерно так:

  1. Э-э, я просто прочитал эту вещь в противоречивом состоянии.
  2. Э-э, я только что изменил эту вещь из 2-х потоков, а теперь это мусор.
  3. Yay! Я узнал о замках
  4. Ого, у меня много замков, и все, кажется, просто зависает иногда, когда у меня много блокировок во вложенном коде.
  5. Hrm. Мне нужно прекратить делать эту блокировку «на лету», мне, кажется, не хватает места; поэтому я должен инкапсулировать их в структуру данных.
  6. Эта структура данных была отличной, но теперь я, кажется, все время блокируюсь, и мой код так же медленен, как и один поток.
  7. переменные состояния являются странными
  8. Это быстро, потому что я сообразил, как я блокирую вещи. Хмм. Иногда данные разлагаются.
  9. Whoa .... InterlockedWhatDidYouSay?
  10. Эй, не смотри замок, я делаю эту вещь как спин-замок.
  11. Переменные условий. Хм ... Понимаю.
  12. Вы знаете, что, как насчет я просто начать думать о том, как работать с этим материалом в совершенно независимых способах, pipelineing своих операций, и имеющие несколько перекрестных зависимостей потоков, как это возможно ...

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

3

что это?

Вкратце, программа, которая может выполняться в параллельном контексте без ошибок, связанных с параллелизмом.

Если ThreadA и ThreadB считывают и/или записывают данные без ошибок и используют надлежащую синхронизацию, то программа может быть потокобезопасной. Это выбор дизайна - создание объекта threadafe может осуществляться несколькими способами, а более сложные типы могут быть потокобезопасными с использованием комбинаций этих методов.

и как и где я могу научиться программировать безопасный код?

boost/libs/thread /, скорее всего, будет хорошим представлением. Тема довольно сложная.

Стандартная библиотека C++ 11 обеспечивает реализацию для блокировок, атомации и потоков - любые хорошо написанные программы, которые используют их, были бы хорошо прочитаны. Стандартная библиотека была смоделирована после внедрения boost.

также, предположим, что у меня есть 2 потока, которые записывают в структуру и другую, которая читает с нее. Это опасно? есть ли что-нибудь, что я должен искать?

Да, это может быть опасно и/или привести к неправильным результатам. Представьте себе, что поток может истекать из своего времени в любой момент, а затем другой поток может затем прочитать или изменить эту структуру - если вы его не защитили, это может быть в середине обновления. Общим решением является блокировка, которая может использоваться для предотвращения доступа другого потока к общим ресурсам во время чтения/записи.

+0

Как я упоминал в предыдущем посте, пожалуйста, проверьте этот stackoverflow.com/a/5125493/1248779 Он говорит, что так вы можете сделать материал потокобезопасным. Я не вижу его. Что различается до и после инкапсуляции? – Kam

+0

@ user1248779 Theo 'int myFunc (struct myState * state)' не является потоковым, если '@a state' может быть записано другим потоком. Если '@a state 'были локальными данными потока, то это было бы поточно-файловым. iow, это не инкапсуляция, которая делает эту программу потокобезопасной - это потому, что объект '@a state' указывает на то, что он является локальным, а не глобальным. Также обратите внимание, что Тео указал, что '@a state' должен быть потоковым локальным. – justin

+0

Так почему он инкапсулирует x и y и создает структуру? в чем польза? пожалуйста, со мной, я действительно этого не вижу – Kam

0

Чтобы ответить на вторую часть вопроса: Представьте себе две темы, как доступ к std::vector<int> data:

//first thread 
if (data.size() > 0) 
{ 
    std::cout << data[0]; //fails if data.size() == 0 
} 

//second thread 
if (rand() % 5 == 0) 
{ 
    data.clear(); 
} 
else 
{ 
    data.push_back(1); 
} 

Выполнить эти нити параллельно и программа зависнет, поскольку std::cout << data[0]; может выполняться непосредственно после data.clear();.

Вам необходимо знать, что в любой точке вашего кода потока поток может быть прерван, например. после проверки этого (data.size() > 0), и другой поток может стать активным. Хотя первый поток выглядит корректно в однопоточном приложении, он не работает в многопоточной программе.

1

Безопасность потока - это простая концепция: безопасно ли выполнять операцию A на одном потоке, в то время как другой поток выполняет операцию B, которая может быть или не быть такой же, как операция A. Это может быть расширено для покрытия многих потоки. В этом контексте, «безопасный» означает:

  • Нет неопределенное поведение
  • Все инварианты структур данных гарантируется не наблюдается нитями

Фактические операции А и В имеют важное значение. Если два потока и читают a plain int переменная, то это нормально. Однако, если какой-либо поток может записать эту переменную, и нет синхронизации, чтобы убедиться, что чтение и запись не могут выполняться вместе, тогда у вас есть гонка данных, которая является неопределенным поведением, и это не потокобезопасный.

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

Вы можете использовать такие вещи, как мьютексы и критические разделы, чтобы предотвратить одновременный доступ к некоторым данным, так что поток записи является единственным потоком, который обращается к данным при его записи, а поток чтения - это единственный поток, который обращается к данным, когда он читает, тем самым предоставляя гарантию, о которой я только что упомянул. Поэтому это позволяет избежать неопределенного поведения, упомянутого выше.

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

Возможно также, что структура данных может быть потокобезопасной для некоторых операций, но не для других. Например, однопользовательская очередь с одним потребителем будет ОК, если один поток нажимает элементы в очереди, а другой выталкивает элементы из очереди, но прерывается, если два потока нажимают элементы, или два потока выталкивают элементы.

В примере, на котором вы ссылаетесь, дело в том, что глобальные переменные неявно разделяются между всеми потоками, и поэтому все обращения должны быть защищены некоторой формой синхронизации (например, мьютексом), если какой-либо поток может их модифицировать. С другой стороны, если у вас есть отдельная копия данных для каждого потока, то этот поток может изменить свою копию, не беспокоясь о параллельном доступе из любого другого потока, и синхронизация не требуется. Конечно, вам всегда нужна синхронизация, если два или более потока будут работать с одними и теми же данными.

Моя книга, C++ Concurrency in Action охватывает то, что означает, что вещи должны быть потокобезопасными, как создавать потокобезопасные структуры данных и примитивы синхронизации C++, используемые для этой цели, такие как std::mutex.

Смежные вопросы