Потоки и простое средство исправления Тупика

Я часто задаюсь вопросом почему статические методы вообще? У них действительно есть свое использование, но методы уровня пакета/пространства имен, вероятно, касались бы 80 из того, для чего используются статические методы.

9
задан PJT 12 December 2009 в 07:04
поделиться

8 ответов

A good simple rule of thumb is to always obtain your locks in a consistent predictable order from everywhere in your application. For example, if your resources have names, always lock them in alphabetical order. If they have numeric ids, always lock from lowest to highest. The exact order or criteria is arbitrary. The key is to be consistent. That way you'll never have a deadlock situation. eg.

  1. Thread 1 locks resource A
  2. Thread 2 locks resource B
  3. Thread 1 waits to obtain a lock on B
  4. Thread 2 waits to obtain a lock on A
  5. Deadlock

The above can never happen if you follow the rule of thumb outlined above. For a more detailed discussion, see the Wikipedia entry on the Dining Philosophers problem.

16
ответ дан 4 December 2019 в 08:15
поделиться

Try to avoid acquiring one lock and trying to acquire another. This can result into circular dependency and cause for deadlock. If it is un-avoidable then at least the order of acquire locks should be predictable.

Use RAII ( to make sure lock is release properly in case of exception as well)

2
ответ дан 4 December 2019 в 08:15
поделиться
  1. If at all possible, design your code so that you never have to lock more then a single mutex/semaphore at a time.
  2. If that's not possible, make sure to always lock multiple mutex/semaphores in the same order. So if one part of the code locks mutex A and then takes semaphore B, make sure that no other part of the code takes semaphore B and then locks mutex A.
7
ответ дан 4 December 2019 в 08:15
поделиться

One way to ensure the ordering that other folks have talked about is to acquire locks in an order defined by their memory address. If at any point, you try to acquire a lock that should have been earlier in the sequence, you release all the locks and start over.

With a little work, it's possible to do this nearly automatically with some wrapper classes around the system primitives.

0
ответ дан 4 December 2019 в 08:15
поделиться

There is no simple deadlock cure.

Acquire locks in agreed order: If all calls acquire A->B->C then no deadlock can occur. Deadlocks can occur only if the locking order differs between the two threads (one acquires A->B the second B->A).

In practice is hard to choose an order between arbitrary objects in memory. On a simple trivial project is possible, but on large projects with many individual contributors is very hard. A partial solution is to create hierarchies, by ranking the locks. All locks in module A have rank 1, all locks in module B have rank 2. One can acquire a lock of rank 2 when helding locks of rank 1, but not vice-versa. Of course you need a framework around the locking primitives that tracks and validates the ranking.

1
ответ дан 4 December 2019 в 08:15
поделиться

Существует множество простых "способов лечения тупиковых ситуаций". Но ни один из них не прост в применении и не работает универсально.

Самый простой из всех, конечно, - «никогда не иметь более одного потока».

Предполагая, что у вас есть многопоточное приложение, все же есть ряд решений:

Вы можете попытаться минимизировать общее состояние и синхронизацию. Два потока, которые работают параллельно и никогда не взаимодействуют, никогда не заблокируются. Тупиковые ситуации возникают только тогда, когда несколько потоков пытаются получить доступ к одному и тому же ресурсу. Почему они это делают? Можно ли этого избежать? Можно ли реструктурировать или разделить ресурс так, чтобы, например, один поток мог писать в него, а другие потоки асинхронно передавали нужные им данные?

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

И, как уже упоминалось в каждом другом ответе, если и когда вы пытаетесь получить блокировки, делайте это в глобальном согласованном порядке. Чтобы упростить это, вы должны попытаться обеспечить, чтобы все блокировки, которые потребуются потоку, были получены как одна операция. Если потоку необходимо получить блокировки A, B и C, он не должен выполнять три вызова lock () в разное время и из разных мест. Вы' Вы запутаетесь, и вы не сможете отследить, какие блокировки удерживаются потоком, а какие еще не установлены, и тогда вы испортите порядок. Если вы можете получить всю необходимую блокировку один раз , то вы можете выделить ее в отдельный вызов функции, который получает N блокировок и делает это в правильном порядке, чтобы избежать взаимоблокировок.

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

Транзакционная память - еще один многообещающий вариант, который может быть проще интегрировать в обычные программы. Но внедрения производственного качества по-прежнему очень редки.

0
ответ дан 4 December 2019 в 08:15
поделиться

Read Deadlock: the Problem and a Solution.

"The common advice for avoiding deadlock is to always lock the two mutexes in the same order: if you always lock mutex A before mutex B, then you'll never deadlock. Sometimes this is straightforward, as the mutexes are serving different purposes, but other times it is not so simple, such as when the mutexes are each protecting a separate instance of the same class".

0
ответ дан 4 December 2019 в 08:15
поделиться

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

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

Я бы сказал,

  • предпочел бы использовать потоки только в качестве оптимизации производительности для многоядерных машин
  • только оптимизировать производительность когда вы уверены, что вам нужна такая производительность
  • , вы можете использовать потоки для упрощения логики программы, но только если вы абсолютно уверены в том, что делаете. Будьте особенно осторожны, и все блокировки ограничиваются очень маленьким фрагментом кода. Не позволяйте новичкам приближаться к такому коду.
  • никогда не используйте потоки в критически важных системах, таких как управление самолетом или управление опасным оборудованием
  • во всех случаях, потоки редко бывают рентабельными из-за более высокой степени отладки и Затраты на обеспечение качества

Если вы решили выполнять потоки или поддерживать существующую кодовую базу:

  • ограничьте все блокировки небольшими и простыми фрагментами кода, которые работают с примитивами
  • , избегайте вызовов функций или перенаправления потока программы туда, где это необходимо. выполнения под блокировкой не сразу видно. Эта функция будет изменена будущими авторами, расширяя диапазон блокировок без вашего контроля.
  • получить блокировки внутри объектов, чтобы уменьшить область блокировки, обернуть небезопасные сторонние объекты с вашими собственными потокобезопасными интерфейсами.
  • никогда не отправлять синхронные уведомления (обратные вызовы) при выполнении под блокировкой
  • использовать только блокировки RAII, чтобы снизить когнитивную нагрузку при размышлениях «как еще мы можем выйти отсюда», например, в исключениях и т. Д.

Некоторые слова о том, как избежать многопоточности.

Однопоточная конструкция обычно включает некоторую функцию сердцебиения, предоставляемую компонентами программы и вызываемую в цикле (называемом циклом сердцебиения), который при вызове дает возможность всем компонентам выполнить следующую работу и снова передать контроль. То, что алгоритмисты любят называть «циклами» внутри компонентов, превратится в конечные автоматы, чтобы определить, что следует делать в следующий раз при вызове. Состояние лучше всего поддерживать как данные-члены соответствующих объектов.

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

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

0
ответ дан 4 December 2019 в 08:15
поделиться
Другие вопросы по тегам:

Похожие вопросы: