Работа с потоками в C#. Часть 1.
От: Алексей Кирюшкин (перевод) Россия http://malgarr.blogspot.com/
Дата: 24.04.07 14:55
Оценка: 1111 (45) +3
Статья:
Работа с потоками в C#. Часть 1.
Автор(ы): Joseph Albahari
Дата: 24.03.2007
Подробно рассматривается работа с потоками — запуск, завершение, прерывание, блокировки, синхронизация, контексты синхронизации, особенности взаимодействия с апартаментами, а также потоковые возможности .NET — потоковые таймеры, пулы потоков, BackgroundWorker, асинхронные методы и делегаты.
В статье использован материал из книги Joseph Albahari, Ben Albahari "C# 3.0 in a Nutshell" — http://www.oreilly.com/catalog/9780596527570/


Авторы:
Алексей Кирюшкин (перевод)

Аннотация:
В первой части статьи рассматриваются основы работы с потоками — запуск, завершение, прерывание, блокировки и базовые сведения о синхронизации.
В статье использован материал из книги Joseph Albahari, Ben Albahari "C# 3.0 in a Nutshell" — http://www.oreilly.com/catalog/9780596527570/
Re: Работа с потоками в C#. Часть 1.
От: _FRED_ Черногория
Дата: 30.05.07 15:05
Оценка:
Здравствуйте, Алексей Кирюшкин (перевод), Вы писали:

АКП>Статья:

АКП>Работа с потоками в C#. Часть 1.
Автор(ы): Joseph Albahari
Дата: 24.03.2007
Подробно рассматривается работа с потоками — запуск, завершение, прерывание, блокировки, синхронизация, контексты синхронизации, особенности взаимодействия с апартаментами, а также потоковые возможности .NET — потоковые таймеры, пулы потоков, BackgroundWorker, асинхронные методы и делегаты.
В статье использован материал из книги Joseph Albahari, Ben Albahari "C# 3.0 in a Nutshell" — http://www.oreilly.com/catalog/9780596527570/


Огорчает нежелание (непонимание ) автора использовать модификатор readonly в объявлении "объекта синхронизации".
Help will always be given at Hogwarts to those who ask for it.
Re: Работа с потоками в C#. Часть 1.
От: .Den Украина  
Дата: 30.05.07 16:52
Оценка:
Здравствуйте, Алексей Кирюшкин (перевод), Вы писали:
class ThreadUnsafe 
{
  static object locker = new object();
  static int x;
 
  static void Increment()
  {
    lock (locker)
      x++;
  }


  static void Assign()
  {
    lock (locker)
      x = 123;
  }
}

Чо за бред?

Из спецификации языка: 5.5 Atomicity of variable references
Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types.
Re[2]: Работа с потоками в C#. Часть 1.
От: Mika Soukhov Stock#
Дата: 30.05.07 18:16
Оценка:
Здравствуйте, .Den, Вы писали:

D>Чо за бред?


D>Из спецификации языка: 5.5 Atomicity of variable references

D>Reads and writes of the following data types are atomic: bool, char, byte, sbyte, short, ushort, uint, int, float, and reference types.

Присваиваение и считывание — одна операция. Только вот x++ это не одна операция, а целых три.
Re[2]: Работа с потоками в C#. Часть 1.
От: Dog  
Дата: 31.05.07 08:17
Оценка:
АКП>>Статья:
АКП>>Работа с потоками в C#. Часть 1.
Автор(ы): Joseph Albahari
Дата: 24.03.2007
Подробно рассматривается работа с потоками — запуск, завершение, прерывание, блокировки, синхронизация, контексты синхронизации, особенности взаимодействия с апартаментами, а также потоковые возможности .NET — потоковые таймеры, пулы потоков, BackgroundWorker, асинхронные методы и делегаты.
В статье использован материал из книги Joseph Albahari, Ben Albahari "C# 3.0 in a Nutshell" — http://www.oreilly.com/catalog/9780596527570/

_FR>Огорчает нежелание (непонимание ) автора использовать модификатор readonly в объявлении "объекта синхронизации".
Ну так и написали бы почему необходимо использовать...
... << RSDN@Home 1.2.0 alpha rev. 669>>
Re[3]: Работа с потоками в C#. Часть 1.
От: Lloyd Россия  
Дата: 31.05.07 08:37
Оценка:
Здравствуйте, Mika Soukhov, Вы писали:

MS>Присваиваение и считывание — одна операция. Только вот x++ это не одна операция, а целых три.


Думаю, .Den неспроста выделил пример с присваиванием.
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[4]: Работа с потоками в C#. Часть 1.
От: Mika Soukhov Stock#
Дата: 31.05.07 08:50
Оценка: +3
Здравствуйте, Lloyd, Вы писали:

L>Здравствуйте, Mika Soukhov, Вы писали:


MS>>Присваиваение и считывание — одна операция. Только вот x++ это не одна операция, а целых три.


L>Думаю, .Den неспроста выделил пример с присваиванием.


Если убрать лок у присваивания, то будет возможно ситуация, что между инкрементированием х он станет равным 123. Задача конечно надумана (лично по мне, ну и что, что будет 123), но видимо автор и хотел донести, что или присваивать или инкрементировать.

Сейчас решил прочитать статью (вернее тот участок ), и увидел вот это

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


Выделенное — неправильно. Но на удивление, сам пример корректен.
Re[3]: Работа с потоками в C#. Часть 1.
От: _FRED_ Черногория
Дата: 31.05.07 08:51
Оценка: 23 (4) +5
Здравствуйте, Dog, Вы писали:

АКП>>>Статья:

АКП>>>Работа с потоками в C#. Часть 1.
Автор(ы): Joseph Albahari
Дата: 24.03.2007
Подробно рассматривается работа с потоками — запуск, завершение, прерывание, блокировки, синхронизация, контексты синхронизации, особенности взаимодействия с апартаментами, а также потоковые возможности .NET — потоковые таймеры, пулы потоков, BackgroundWorker, асинхронные методы и делегаты.
В статье использован материал из книги Joseph Albahari, Ben Albahari "C# 3.0 in a Nutshell" — http://www.oreilly.com/catalog/9780596527570/

_FR>>Огорчает нежелание (непонимание ) автора использовать модификатор readonly в объявлении "объекта синхронизации".
Dog>Ну так и написали бы почему необходимо использовать...

Не то, что бы необходимо, но, ИМХО, GoodPractice. Значение "объекту синхронизации" присваивается при объявлении:
object syncRoot = new object();

что бы в местах вызова перед испольхованием не беспокоиться о [потокобезопасной] инициализации. Переприсваивать значение данной переменной вредно: не удастся снять существующие локи да и где-то может запоминаться ссылка на значение данной переменной.

А раз переприсваивать значение не нужно и даже опасно, то не надо:
  • ни рассчитывать на разумность людей, которые будут, возможно, пользоваться вашим кодом;
  • ни предупреждать, например, в комментариях, что нельзя перезаписывать значение вот этой вот переменной,
    когда можно попросту это запретить:
    readonly object syncRoot = new object();

    Теперь, даже если кому-то случайно\по незнанию захочется что-то своё записать в это поле, он узнает, что так делать нельзя.

    После знакомства с последними тенденциями (Linq, Nemerle), я процентов 90 полей объявляю как readonly, ибо позволяет избежать множества проблем, самая первая из которых "не null ли в этом поле"? Во-вторых, класс, содержащий только readonly поля автоматически становится потокобезопасным. Есть и много других (большей частью эстетических) плюсов. Общее правило такое: не объявлять поле как НЕ readonly только если его ну никак нельзя сделать readonly.

    Да, я знаю, что в Framework Design Guidelines сказано, что этот модификатор рекомендуется использовать только с immutable-типами, но не согласен: модификотором readonly я защищаю не столько "содержимое", "данные" объекта, сколько ссылку на него. В C++ аналогом readonly я бы назвал
    T* const field; //константный указатель
    , в то время как многим хочется видеть
    const T* field; //указатель на константу
  • Help will always be given at Hogwarts to those who ask for it.
    Re[5]: Работа с потоками в C#. Часть 1.
    От: Lloyd Россия  
    Дата: 31.05.07 08:55
    Оценка:
    Здравствуйте, Mika Soukhov, Вы писали:

    L>>Думаю, .Den неспроста выделил пример с присваиванием.


    MS>Если убрать лок у присваивания, то будет возможно ситуация, что между инкрементированием х он станет равным 123. Задача конечно надумана (лично по мне, ну и что, что будет 123), но видимо автор и хотел донести, что или присваивать или инкрементировать.


    Да, действительно все правильно.
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Re[5]: Работа с потоками в C#. Часть 1.
    От: andrey.bond  
    Дата: 31.05.07 09:08
    Оценка:
    Здравствуйте, Mika Soukhov, Вы писали:

    MS>Как правило, любое поле, доступное нескольким потокам, должно читаться и записываться с блокировкой. Даже в самом простом случае, операции присваивания одиночному полю, необходима синхронизация. В следующем классе ни приращение, ни присваивание не потокобезопасны:

    MS>[/q]

    MS>Выделенное — неправильно. Но на удивление, сам пример корректен.


    Почему?
    Re[6]: Работа с потоками в C#. Часть 1.
    От: Mika Soukhov Stock#
    Дата: 31.05.07 09:14
    Оценка:
    Здравствуйте, andrey.bond, Вы писали:

    AB>Здравствуйте, Mika Soukhov, Вы писали:


    MS>>Как правило, любое поле, доступное нескольким потокам, должно читаться и записываться с блокировкой. Даже в самом простом случае, операции присваивания одиночному полю, необходима синхронизация. В следующем классе ни приращение, ни присваивание не потокобезопасны:

    MS>>[/q]

    MS>>Выделенное — неправильно. Но на удивление, сам пример корректен.


    AB>Почему?


    Потому что, как было сказано выше, считывание и запись — атомарные операции. Блокировки нужно вводить, когда вводится третье действие — операция над данными, которое содержит поле.
    Re[4]: Работа с потоками в C#. Часть 1.
    От: rsn81 Россия http://rsn81.wordpress.com
    Дата: 31.05.07 09:24
    Оценка:
    Здравствуйте, _FRED_, Вы писали:

    Если резюмировать: безопасность инициализации по умолчанию является потоковой — и это может жестко обеспечить readonly. Так, например, в Java тоже делает final.

    Может быть неправ (по-крайнем мере в Java это так), но мне кажется в примерах в принципе все корректно. Объект синхронизации является статическим полем, а соответственно инициализируется при первом обращении к нему, то есть как раз когда происходит инициализация класса. Соответственно, потоковая безопасность инициализации и здесь действует.
    ... << RSDN@Home 1.2.0 alpha rev. 677>>
    Re[5]: Работа с потоками в C#. Часть 1.
    От: Mika Soukhov Stock#
    Дата: 31.05.07 09:30
    Оценка: +3
    Здравствуйте, rsn81, Вы писали:

    R>Здравствуйте, _FRED_, Вы писали:


    R>Если резюмировать: безопасность инициализации по умолчанию является потоковой — и это может жестко обеспечить readonly. Так, например, в Java тоже делает final.


    R>Может быть неправ (по-крайнем мере в Java это так), но мне кажется в примерах в принципе все корректно. Объект синхронизации является статическим полем, а соответственно инициализируется при первом обращении к нему, то есть как раз когда происходит инициализация класса. Соответственно, потоковая безопасность инициализации и здесь действует.


    Fred целую научную статью написал . Лично я объясняю проще — ридонли нужно помечать все, что онициализирует один раз в конструкторе (статическом или обычном). И не важно какой юз кейс — патоки, шматоки, гуи.

    [Филосовствуя] Вообще, нужно было сразу делать поля неизменяемыми. И, если они должны изменятся, нужно явно помечать поле как mutable.
    Re[6]: Работа с потоками в C#. Часть 1.
    От: rsn81 Россия http://rsn81.wordpress.com
    Дата: 31.05.07 09:37
    Оценка: 1 (1)
    Здравствуйте, Mika Soukhov, Вы писали:

    Я имел в виду, что final в Java не просто immutable-модификатор, но еще и играет роль в потоковой безопасности. Подозреваю, что в C# работает с readonly аналогично. Если не прав, пните.

    Безопасность инициализации

    Новая модель памяти JMM также старается предоставить новые гарантии безопасности инициализации. То есть, если объект соответствующим образом сконструирован (то есть ссылка на объект не опубликована, пока конструирование не завершено), тогда все потоки будут видеть значения его полей final, которые были установлены конструктором, независимо от того, используется ли синхронизация, чтобы передать ссылку из одного потока другому. Более того, переменные, которые могут быть доступны через поле final должным образом сконструированного объекта, например поля объекта, на который ссылается поле final, также гарантированно видимы другим потокам. Это значит, что если поле final содержит ссылку на, скажем, LinkedList, кроме видимости правильного значения ссылки другим потокам, содержимое этого LinkedList во время конструирования будет доступно другим потокам без синхронизации. Результат этого — значительное усиление значения final, то есть поля final могут быть безопасно доступны без синхронизации, и компиляторы могут предположить, что поля final не изменятся, и следовательно смогут оптимизировать множественные выборки.

    Final значит final

    Механизм, по которому поля final могли менять свое значение под старой моделью памяти был описан в Части 1 — в отсутствии синхронизации другой поток мог сначала видеть значение по умолчанию для поля final, а позже видеть правильное значение.

    Под новой моделью памяти есть что-то похожее на отношения по схеме происходит-прежде между записью поля final в конструкторе и начальной загрузкой общедоступной ссылки на этот объект в другом потоке. Когда конструирование завершается, все записи в полях final (и переменных, доступных косвенно через эти поля final) становятся "заблокированными", и любой поток, который содержит ссылку на этот объект, после блокировки будет гарантированно видеть все заблокированные поля для всех заблокированных значений. Записи, которые инициализируют поля final, не будут перераспределяться с операциями, следующими за блокировкой, связанной с конструктором.

    (c) Брайан Гетц, Теория и практика Java: Исправление модели памяти Java, часть 2

    По поводу статической инициализации в той же статье:

    Вместо блокировки с двойной проверкой используйте идиому Initialize-on-demand Holder Class, которая обеспечивает ленивую инициализацию, потокобезопасность, и работает быстрее и не так запутанно как блокировка с двойной проверкой:

    Листинг 2. Идиома Initialize-On-Demand Holder Class

    private static class LazySomethingHolder {
      public static Something something = new Something();
    }
    
    ...
    
    public static Something getInstance() {
      return LazySomethingHolder.something;
    }

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

    ... << RSDN@Home 1.2.0 alpha rev. 677>>
    Re[7]: Работа с потоками в C#. Часть 1.
    От: tol05  
    Дата: 31.05.07 09:43
    Оценка:
    Здравствуйте, Mika Soukhov, Вы писали:

    MS>Потому что, как было сказано выше, считывание и запись — атомарные операции. Блокировки нужно вводить, когда вводится третье действие — операция над данными, которое содержит поле.


    Хочу спросить: ведь Ваше высказывание справедливо только для однопроцессорных систем? Рихтер в своей книге писал, что для многопроцессорных систем из-за наличия кеширования данных процессорами могут возникнуть проблемы, особенно при записи. Поэтому он рекомендует запись обязательно синхронизировать, а чтение — на усмотрение. Какое Ваше мнение по этому поводу?
    ... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
    Re[6]: Работа с потоками в C#. Часть 1.
    От: rsn81 Россия http://rsn81.wordpress.com
    Дата: 31.05.07 09:43
    Оценка:
    Здравствуйте, Mika Soukhov, Вы писали:

    MS>Вообще, нужно было сразу делать поля неизменяемыми. И, если они должны изменятся, нужно явно помечать поле как mutable.

    Ага, подход Nemerle в этом отношении более привлекателен.

    Я решаю эту проблему автоматическим рефакторингом: среда разработки пробегает по коду и все переменные, которые может, делает неизменяемыми.
    ... << RSDN@Home 1.2.0 alpha rev. 677>>
    Re[8]: Работа с потоками в C#. Часть 1.
    От: rsn81 Россия http://rsn81.wordpress.com
    Дата: 31.05.07 09:48
    Оценка:
    Здравствуйте, tol05, Вы писали:

    Атомарность операции и означает, что после ее выполнения данные будут доступны всем потокам, т.е. не будут находиться в регистрах, локальном кэше процессора и т.п.
    ... << RSDN@Home 1.2.0 alpha rev. 677>>
    Re[5]: Работа с потоками в C#. Часть 1.
    От: _FRED_ Черногория
    Дата: 31.05.07 09:50
    Оценка:
    Здравствуйте, rsn81, Вы писали:

    R>Если резюмировать: безопасность инициализации по умолчанию является потоковой — и это может жестко обеспечить readonly. Так, например, в Java тоже делает final.


    R>Может быть неправ (по-крайнем мере в Java это так), но мне кажется в примерах в принципе все корректно.


    Некоректно, с моей точки зрения, то, что ничто (кроме здравого смысла) не мешает мне в какой-нить хакерской функции переприсвоить значение "объекта синхронизации":
    class Test
    {
      static object syncLock = new object();
    
      static void Proc1() {
        lock(syncLock) {
          // bla-bla-bla
        }//lock
      }
    
      static void Proc2() {
        lock(syncLock) {
          // bla-bla-bla
        }//lock
      }
    
      static void ProcXXX() {
        lock(syncLock) {
          syncLock = 8; // Без readonly этому нично не мешает. 
                        // Грабли в этом переприсвоении читателю предлагается найти самому ;о)
        }//lock
      }
    }
    Help will always be given at Hogwarts to those who ask for it.
    Re[8]: Работа с потоками в C#. Часть 1.
    От: Максим Зелинский  
    Дата: 31.05.07 09:51
    Оценка: +1
    Здравствуйте, tol05, Вы писали:

    T>Здравствуйте, Mika Soukhov, Вы писали:


    MS>>Потому что, как было сказано выше, считывание и запись — атомарные операции. Блокировки нужно вводить, когда вводится третье действие — операция над данными, которое содержит поле.


    T>Хочу спросить: ведь Ваше высказывание справедливо только для однопроцессорных систем? Рихтер в своей книге писал, что для многопроцессорных систем из-за наличия кеширования данных процессорами могут возникнуть проблемы, особенно при записи. Поэтому он рекомендует запись обязательно синхронизировать, а чтение — на усмотрение. Какое Ваше мнение по этому поводу?

    Не смешивайте. Проблемы с кэшем решаются через volitale и ему подобные конструкции, а блокировать ли на чтение, это уже другой вопрос, не имеющий отношение к кэшу.
    ... << RSDN@Home 1.2.0 alpha rev. 679>>
    Re[8]: Работа с потоками в C#. Часть 1.
    От: Mika Soukhov Stock#
    Дата: 31.05.07 09:58
    Оценка:
    Здравствуйте, tol05, Вы писали:

    T>Здравствуйте, Mika Soukhov, Вы писали:


    MS>>Потому что, как было сказано выше, считывание и запись — атомарные операции. Блокировки нужно вводить, когда вводится третье действие — операция над данными, которое содержит поле.


    T>Хочу спросить: ведь Ваше высказывание справедливо только для однопроцессорных систем? Рихтер в своей книге писал, что для многопроцессорных систем из-за наличия кеширования данных процессорами могут возникнуть проблемы, особенно при записи.


    Лечиться write barrier и применяется volitile. Наверное и для многоядерных такое справедливо. У них тоже свой несколько кешей.

    T>Поэтому он рекомендует запись обязательно синхронизировать, а чтение — на усмотрение. Какое Ваше мнение по этому поводу?


    Обычные сценарии таковы, что:

    1) Если поле присваивают несколько раз из внешнего кода, то тип, содержищий данное поле, является простой структурой данных. И ничего там не нужно синхронизировать вообще.
    2) Внешний код вообще не может присваивать значение полю, если внутри идет обработка данных, которое содержит это поле. Такое поле присваивается один раз при констуировании объекта (или типа).

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