andrzejn: (Curious)
[personal profile] andrzejn
В .NET Framework есть аналог synchronized блоков в Java - в VB.NET это блок SyncLock, а в C# он называется lock. Он гарантирует, что несколько потоков одновременно не будут исполнять код внутри блока - пока один из потоков находится внутри, остальные претенденты ждут своей очереди.

Но есть ситуация, в которой SyncLock не работает.

Делаем такую конструкцию:

1. Метод, который всегда вызывается только в основном (интерфейсном) потоке. Например, обработчик тиков Windows.Forms.Timer или просто метод, который другие потоки вызывают через Control.Invoke.

2. В теле метода размещаем блок SyncLock.

3. Внутри метода, среди прочего, делаем что-нибудь длительное, вызывая в промежутках Application.DoEvents.

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

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

Как это получается?

Одной фразой проблема объясняется так: SyncLock обеспечивает блокировку разных потоков, но не рекурсивных вызовов в одном и том же потоке.

И Forms.Timer, и Control.Invoke работают через очередь сообщений Windows. Да, ту самую очередь сообщений, которая тянется ещё с ранних 16-битных версий Windows. Пока одно сообщение обрабатывается, остальные ждут в очереди.

Вызов Application.DoEvents - это требование разобрать и обработать сообщения, накопившиеся в очереди. Пока вся очередь не разберётся, DoEvents не вернёт управление. Разбор очереди происходит в том же самом потоке управления, откуда вызывали DoEvents.

Если одним из сообщений в очереди является требование вызвать наш метод, то получается рекурсивный вызов: метод A вызывает DoEvents, который вызывает метод A. И вот мы снова вошли в наш метод - и SyncLock не заблокировал второй вход (и хорошо, что не заблокировал, а то бы тут всё и зависло). Но если вы такого не ожидали, можете нарваться на неприятный сюрприз.

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

Date: Tuesday, 17 July 2007 19:09 (UTC)
From: [identity profile] softmaster.livejournal.com
А ещё лучше - никогда не вызывать DoEvents, а что-нибудь длительное делать в BackgroundWorker.

Date: Tuesday, 17 July 2007 19:23 (UTC)
From: [identity profile] marrch-caat.livejournal.com
Ежу ясно. Но вообще да, обратить на это внимание стоило, спасибо.
Порадовало, что "дальше не программистам неинтересно". А до того - интересно было, стало быть? :)

Date: Tuesday, 17 July 2007 19:53 (UTC)
From: [identity profile] acode.livejournal.com
doevents внутри lock-а -- вот хороший пример того, что матерый программер сумеет наступить на грабли даже в одном потоке!

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

Date: Thursday, 19 July 2007 14:57 (UTC)
From: [identity profile] a-bronx.livejournal.com
+100. Я уже зарёкся так делать.

Date: Wednesday, 18 July 2007 01:41 (UTC)
From: [identity profile] arnilaarn.livejournal.com
Жуть какая. Я был лучшего мнения о .нете

Date: Thursday, 19 July 2007 06:18 (UTC)
netch: (Default)
From: [personal profile] netch
А чего сразу мнение портить? Оно-то честно отработало необходимую для подложки функциональность.

Date: Friday, 20 July 2007 15:05 (UTC)
From: [identity profile] arnilaarn.livejournal.com
Дык оно либо должно было вывалится при попытке войти в мютекс рекурсивно, либо в вызове функции что-то должно отображать каким-то образом факт, что мютекс рекурсивный. А то ни рыба, ни мясо. Хотя бы, как в линуксе - хочешь рекурсивный мютекс - включи его специально pthread_mutexattr_settype(... PTHREAD_MUTEX_RECURSIVE ...). Хочешь получить ошибку - пожалуйста, PTHREAD_MUTEX_ERRORCHECK. Тоже не идеально (по умолчанию undefined behavior), но по крайней мере в документации это всё прямо в глаза бросается. А в дотнете можно сделать нерекурсивным?

Date: Wednesday, 18 July 2007 06:46 (UTC)
From: [identity profile] lee-bey.livejournal.com
А-а-а, блин!!! Линукса на них нет!!!
Все мое естество против! Оно говорит, что внутри спинлоков решедулинга не бывает.... иначе -- кернел упс... %)

Вообще-то да, можно сделать SyncLock маленьким (без DoEvents-ов), в котором только флажок устанавливаешь и во внутреннюю очередь потоки кладешь --- и будить эти потоки потом.. но разве какой-нибудь стандартной фичи для этого нет?
типа, waitqueue? или как там ее?

Date: Thursday, 19 July 2007 06:17 (UTC)
netch: (Default)
From: [personal profile] netch
> Оно говорит, что внутри спинлоков решедулинга не бывает.... иначе -- кернел упс... %)

SyncLock не спинлок, а обыкновенный мьютекс. Естественно, с разрешением решедулинга и очередью ожидающих. Если уж вспоминать про линукс, то его надо сравнивать или с тем зверем, который лочится по down() (в ядре), или с тем, который pthread_mutex_lock().

Но при этом, судя по рассказу, он рекурсивный (то есть намеренно сделан так что позволяет одной нити лочить много раз, и держит для этого счётчик).

> Вообще-то да, можно сделать SyncLock маленьким (без DoEvents-ов),

DoEvents - это не в нём, а в прикладном коде. Никто не заставляет приложение отрабатывать очередь, просто сидя в ожидании мьютекса - это был бы полный изврат.:)

> типа, waitqueue? или как там ее?

Конечно, есть. Иначе бы не работало.

Date: Wednesday, 18 July 2007 10:42 (UTC)
From: [identity profile] stahman.livejournal.com
Спасибо.

Date: Thursday, 19 July 2007 06:11 (UTC)
netch: (Default)
From: [personal profile] netch
> SyncLock обеспечивает блокировку разных потоков, но не рекурсивных вызовов в одном и том же потоке.

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

Profile

andrzejn: (Default)
Андрій Новосьолов

March 2026

M T W T F S S
       1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16171819202122
23242526272829
3031     

Most Popular Tags

-

Style Credit

Expand Cut Tags

No cut tags
Page generated Monday, 16 March 2026 08:27
Powered by Dreamwidth Studios