Что происходит с блокировками объектов, переданных другим потокам? - PullRequest
1 голос
/ 21 сентября 2010

Я не совсем уверен, как это сформулировать, поэтому я просто вставлю свой код и задам вопрос:

private void remoteAction_JobStatusUpdated(JobStatus status) {
    lock (status) {
        status.LastUpdatedTime = DateTime.Now;
        doForEachClient(c => c.OnJobStatusUpdated(status));
        OnJobStatusUpdated(status);
    }
}

private void doForEachClient(Action<IRemoteClient> task) {
    lock (clients) {
        foreach (KeyValuePair<RemoteClientId, IRemoteClient> entry in clients) {
            IRemoteClient clientProxy = entry.Value;
            RemoteClientId clientId = entry.Key;
            ThreadPool.QueueUserWorkItem(delegate {
                try {
                    task(clientProxy);
#pragma warning disable 168
                } catch (CommunicationException ex) {
#pragma warning restore 168
                    RemoveClient(clientId);
                }
            });
        }
    }
}

Предположим, что любой другой код, который изменяет объект status, получитсначала заблокируйте его.

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

Иными словами, когда истекает срок действия оператора lock (status) или вызывает снятие блокировки?

Ответы [ 2 ]

5 голосов
/ 21 сентября 2010

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

В вашем случае кажется, что у вас выполняется основной поток. Он заблокирует как экземпляры status, так и clients, прежде чем будут запущены новые задачи, выполняемые в отдельных потоках. Если какой-либо код в новых потоках хочет получить блокировку либо на status, либо на clients, ему придется подождать, пока основной поток освободит обе блокировки, оставив оба блока lock. Это происходит, когда remoteAction_JobStatusUpdated возвращается.

Вы передаете объект status каждому рабочему потоку, и они все могут делать с этим объектом все, что хотят. Оператор lock (status) никоим образом не защищает экземпляр status. Однако если какой-либо из потоков попытается выполнить lock (status), он будет блокироваться, пока основной поток не снимет блокировку.

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

lock (status) {
  ...
  lock (clients) {
    ...
  }

}

Другой поток выполняет следующий код, где блокировки получаются в обратной последовательности:

lock (clients) {
  ...
  lock (status) {
    ...
  }

}

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

В общем, я бы посоветовал вам инкапсулировать ваше общее состояние в отдельный класс и сделать доступ к нему потокобезопасным:

class State {

  readonly Object locker = new Object();

  public void ModifyState() {
    lock (this.locker) {
      ...
    }
  }

  public String AccessState() {
    lock (this.locker) {
      ...
      return ...
    }
  }

}

Вы также можете пометить ваши методы с помощью атрибута [MethodImpl (MethodImpl.Synchronized)] * ​​, но у него есть свои подводные камни, так как он будет окружать метод lock (this), что в общем случае не рекомендуется .

Если вы хотите лучше понять, что происходит за кулисами оператора lock, вы можете прочитать статью Безопасная синхронизация потоков в журнале MSDN.

0 голосов
/ 21 сентября 2010

Количество блокировок, конечно, не истекает само по себе, блокировка будет действовать до закрывающей скобки оператора lock (..) {}

...