Зачем Monitor.Pulse нужен заблокированный мьютекс? (.Сеть) - PullRequest
1 голос
/ 24 декабря 2009

Monitor.Pulse и PulseAll требует, чтобы блокировка, с которой он работает, была заблокирована во время вызова. Это требование кажется ненужным и вредным для производительности. Моей первой идеей было то, что это привело к 2 потерянным переключениям контекста, но это было исправлено с помощью nobugz ниже (спасибо). Я все еще не уверен, включает ли он потенциал для потерянных переключателей контекста, так как другие потоки, которые ожидали на мониторе, уже доступны для планировщика, но если они запланированы, они будут только возможность выполнить несколько инструкций, прежде чем попасть в мьютекс, и снова придется переключать контекст. Это выглядело бы намного проще и быстрее, если бы блокировка была разблокирована до вызова Monitor.Pulse.

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

Редактировать : Я понимаю, что блокировка необходима для защиты общего ресурса, который обычно изменяется перед Monitor.Pulse. Я пытался сказать, что эта блокировка могла быть разблокирована после доступа к ресурсу, но до Pulse, учитывая, что Monitor будет поддерживать это. Это поможет ограничить блокировку самым коротким временем, в течение которого осуществляется доступ к общему ресурсу. Как таковой:

void f(Item i)
{
  lock(somequeue) {
    somequeue.add(i);
  }
  Monitor.Pulse(somequeue);  // error
}

Ответы [ 4 ]

2 голосов
/ 24 декабря 2009

Ваше предположение, что вызов Pulse () вызывает переключение потоков, неверно. Он просто перемещает поток из очереди ожидания в готовую очередь. Вызов Exit () делает переключение на поток, который находится первым в очереди готовности.

2 голосов
/ 24 декабря 2009

Причина связана с барьерами памяти и гарантией безопасности потоков.

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

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

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

Для хорошего обсуждения вопросов, см .: http://www.albahari.com/threading/part4.aspx#_Wait_and_Pulse

1 голос
/ 21 февраля 2013

Я нашел ответ в этой статье:

http://research.microsoft.com/pubs/64242/implementingcvs.pdf

говорится:

Поскольку мы рассматриваем этот уровень реализации потоков, я следует указать на одну последнюю проблему производительности и что делать с Это. Если сигнал вызывается с удержанной блокировкой, и если вы работаете на мультипроцессоре вновь пробудившийся поток весьма вероятно начать бегать немедленно. Это заставит его снова заблокировать несколько инструкции позже в (2), когда он хочет заблокировать m. Если хотите Чтобы избежать этих дополнительных перераспределений, необходимо организовать передачу поток непосредственно из очереди условных переменных в очередь темы ждут м. Это особенно важно в Java или C #, оба требуют, чтобы m удерживался при вызове сигнала или широковещательной передачи.

Статья в целом немного расплывчата и не упоминает многих деталей реализации, скорее это псевдо / академический уровень. Но, видимо, парни, написавшие его, несут ответственность за фактическую реализацию .net.

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

0 голосов
/ 21 января 2011

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

  1. Условная проверка указывает на необходимость ожидания.
  2. Другой поток изменяет условие, поэтому ожидание не требуется, а затем выполняет Pulse или PulseAll.
  3. Первый поток, заметив, что ожидание было необходимо, выполняет Ожидание.

Как только эта последовательность событий происходит, вполне возможно, что ничто никогда не будет снова активировать блокировку (если только не возникает ситуация, когда ожидание снова будет необходимо, и снова больше не будет необходимости). Таким образом, поток № 1 может ждать вечно события, которое никогда не наступит.

Помещение условной проверки и Ожидания в блокировку позволяет избежать этой опасности, поскольку другой поток не сможет изменить условие между временем проверки условия и временем начала Ожидания. Следовательно, другой поток, который изменяет Условие и выполняет Импульс, может быть уверен, что первый поток либо проверил условие после того, как оно было изменено (и, таким образом, избежал ожидания), либо выполнял Ожидание при выполнении Импульса (и, таким образом, смог возобновить ).

...