Страницы

Поиск по вопросам

среда, 5 февраля 2020 г.

Нужен ли lock в этом коде?

#c_sharp #многопоточность


Подскажите пожалуйста нужен ли lock в этом коде, если я собираюсь использовать его
в Parallel.ForEach?
Пример кода с lock:

private IEnumerable<(Byte[] part_bytes, Int32 part_number)> GetPartsFile(FileStream
file_stream)
    {
        file_stream.Seek(offset: 0, origin: SeekOrigin.Begin);
        for (Int32 index = 0, part_number = 0; index < file_stream.Length; index
+= DefaultCopyBufferSize, part_number++)
        {
            lock (this)
            {
                Byte[] bytes = new Byte[DefaultCopyBufferSize];
                Int32 readed_bytes = file_stream.Read(array: bytes, offset: index,
count: DefaultCopyBufferSize);
                if (readed_bytes < DefaultCopyBufferSize)
                {
                    Byte[] end_bytes = new Byte[readed_bytes];
                    Buffer.BlockCopy(src: bytes, srcOffset: 0, dst: end_bytes, dstOffset:
0, count: readed_bytes);
                    yield return (end_bytes, part_number);
                }
                else
                {
                    yield return (bytes, part_number);
                }
            }
        }
    }


Пример использования в Parallel.ForEach:

Parallel.ForEach(
source: GetPartsFile(file_stream: zip_file),
body: tuple =>
{
    //тут ещё какие то действия
});

    


Ответы

Ответ 1



Именно для работы метода Parallel.ForEach оператор lock в этом коде не нужен. Параметр source - источник - выполняется в одном потоке. А вот body - тело - может выполняться в нескольких потоках (а может и в одном). Отмечу, что блокировака на this является опасной. Если кто-либо ещё возьмёт лок на этот же объект этого класса, то это может привести к дедлоку.

Ответ 2



По поводу SynchronizationLockException. Ваш метод возвращает IEnumerable. Когда вы указываете этот метод в качестве источника данных для Parallel.ForEach(), это значит, что разные элементы последовательности могут быть запрошены и обработаны разными потоками. Но тут в дело вступает yield return. При наличии такой конструкции итератор работает немного по-другому: Метод начинает выполняться, происходит первая итерация цикла. Метод возвращает первый элемент последовательности, при этом первая итерация цикла еще не закончена. При запросе следующего элемента завершается первая итерация, начинается вторая, возвращается второй элемент. Вторая итерация при этом тоже не закончена. И так по кругу. Теперь если скомбинировать Parallel.ForEach(), yield return и lock, получим такой сценарий: Первый поток запрашивает первый элемент: выполняется первая итерация цикла, захватывается лок, возвращается первый элемент. Лок не освобождается. Второй поток запрашивает следующий элемент: завершается первая итерация, код пытается освободить лок и мы получаем исключение SynchronizationLockException, которое говорит о том, что поток попытался вызвать метод монитора, которым он не владеет. Потому что в монитор входил первый поток, а выйти из него пытается второй поток.

Комментариев нет:

Отправить комментарий