Подтвердить что ты не робот

Что такое многопоточные ДО и DONT?

Я использую свои новые знания о потоках повсюду и получаю много сюрпризов

Пример:

Я использовал потоки для добавления чисел в массив. И результат был другим время. Проблема в том, что все мои потоки обновлялись переменные и не синхронизированы.

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

sidenote:
(Я переименовал свою программу thread_add.java в thread_random_number_generator.java: -)

4b9b3361

Ответ 1

В многопоточной среде вы должны заботиться о синхронизации, так что два потока не сжимают состояние, одновременно выполняя модификации. В противном случае у вас могут быть условия гонки в вашем коде (например, см. печально известная катастрофа Therac-25). Вам также нужно расписание потоки для выполнения различных задач. Затем вам необходимо убедиться, что ваша синхронизация и планирование не вызывают тупик, где несколько потоков будут ждать друг друга на неопределенный срок.

Синхронизация

Что-то простое, как увеличение счетчика, требует синхронизации:

counter += 1;

Предположим, что эта последовательность событий:

  • counter инициализируется 0
  • поток A извлекает counter из памяти в CPU (0)
  • контекстный переключатель
  • поток B извлекает counter из памяти в cpu (0)
  • поток B увеличивает counter на cpu
  • поток B возвращает counter из процессора в память (1)
  • контекстный переключатель
  • поток A увеличивает counter на cpu
  • поток A отпишет counter из процессора в память (1)

В этот момент counter равен 1, но оба потока попытались его увеличить. Доступ к счетчику должен быть синхронизирован каким-то механизмом блокировки:

lock (myLock) {
  counter += 1;
}

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

  • counter инициализируется 0
  • поток A получает myLock
  • контекстный переключатель
  • поток B пытается получить myLock, но должен ждать
  • контекстный переключатель
  • поток A извлекает counter из памяти в CPU (0)
  • поток A увеличивает counter на cpu
  • поток A отпишет counter из процессора в память (1)
  • поток A выпускает myLock
  • контекстный переключатель
  • поток B получает myLock
  • поток B извлекает counter из памяти в CPU (1)
  • поток B увеличивает counter на cpu
  • поток B возвращает counter из процессора в память (2)
  • поток B выпускает myLock

В этот момент counter равен 2.

Планирование

Планирование - еще одна форма синхронизации, и вам нужно использовать механизмы синхронизации потоков, такие как события, семафоры, передача сообщений и т.д., чтобы запускать и останавливать потоки. Вот упрощенный пример в С#:

AutoResetEvent taskEvent = new AutoResetEvent(false);

Task task;

// Called by the main thread.
public void StartTask(Task task) {
  this.task = task;
  // Signal the worker thread to perform the task.
  this.taskEvent.Set();
  // Return and let the task execute on another thread.
}

// Called by the worker thread.
void ThreadProc() {
  while (true) {
    // Wait for the event to become signaled.
    this.taskEvent.WaitOne();
    // Perform the task.
  }
}   

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

Тупик

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

public void f() {
  lock (lock1) {
    lock (lock2) {
      // Do something
    }
  }
}

В другой точке вы приобретаете lock2 до lock1:

public void g() {
  lock (lock2) {
    lock (lock1) {
      // Do something else
    }
  }
}

Посмотрим, как это может зайти в тупик:

  • поток A вызывает f
  • поток A получает lock1
  • контекстный переключатель
  • поток B вызывает g
  • поток B получает lock2
  • поток B пытается получить lock1, но должен ждать
  • контекстный переключатель
  • поток A пытается получить lock2, но должен ждать
  • контекстный переключатель

В этот момент нитки A и B ждут друг друга и запираются.

Ответ 2

Есть два типа людей, которые не используют многопоточность.

1) Те, кто не понимает концепцию и не имеют понятия, как ее программировать. 2) Те, которые полностью понимают концепцию и знают, как трудно это сделать правильно.

Ответ 3

Я бы сделал очень вопиющее выражение:

НЕ используйте для использования разделяемой памяти.

DO использовать передачу сообщений.

Как общий совет, попробуйте ограничить количество общего состояния и предпочесть больше архитектуры, управляемой событиями.

Ответ 4

Я не могу привести вам примеры, кроме того, что указал на вас в Google. Найдите основы потоков, синхронизацию потоков, и вы получите больше обращений, чем вы знаете.

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

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

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

Эти 2 являются подавляющим большинством проблем с потоками. Ответ, как правило, заключается в том, чтобы зафиксировать как можно более короткое время и удерживать только 1 блокировку за раз.

Ответ 5

Я замечаю, что вы пишете в java и что никто больше не упоминает книги, поэтому Java Concurrency На практике должна быть ваша многопоточная библия,

Ответ 6

DONT использовать глобальные переменные

DONT использовать многие блокировки (в лучшем случае вообще нет - хотя практически невозможно)

DONT старайтесь быть героем, внедряя сложные сложные протоколы MT

DO используют простые парадигмы. I. Поделитесь обработкой массива с n срезами того же размера - где n должно быть равно числу процессоров

DO проверьте свой код на разных компьютерах (используя один, два, несколько процессоров)

DO использовать атомные операции (например, InterlockedIncrement() и т.п.)

Ответ 7

- Каковы некоторые известные проблемы с потоками? -

- Какую осторожность следует соблюдать при использовании потоков? -

Использование многопоточности на однопроцессорной машине для обработки нескольких задач, где каждая задача занимает примерно одно и то же время, не всегда очень эффективна. Например, вы можете решить создать в своей программе десять потоков, чтобы обрабатывать десять отдельных задач, Если для выполнения каждой задачи требуется около 1 минуты, и вы используете десять потоков для выполнения этой обработки, у вас не будет доступа к результатам любой задачи за все 10 минут. Если вместо этого вы обрабатывали одни и те же задачи, используя только один поток, вы увидите первый результат за 1 минуту, следующий результат - через 1 минуту и ​​т.д. Если вы можете использовать каждый результат, не полагаясь на все готовые результаты одновременно, нить может быть лучшим способом реализации программы.

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

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

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

Использование нескольких потоков для использования машины с несколькими процессорами в теории кажется хорошей идеей, но на практике вам нужно быть осторожным. Чтобы получить какие-либо существенные преимущества в производительности, вам, возможно, придется справиться с балансировкой потоков.

- Приведите примеры. -

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

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

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

И да, этот пример приходит непосредственно из моего собственного опыта: -)

Ответ 8

YAGNI

Самое главное помнить: вам действительно нужно многопоточность?

Ответ 9

Я согласен с почти всеми ответами до сих пор.

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

  • Используя поточно-статические переменные (хотя и не перегружайтесь, это будет потреблять больше памяти на поток, в зависимости от вашего O/S).
  • Упаковывать все состояние, используемое каждым потоком в класс, а затем гарантировать, что каждый поток получает ровно один экземпляр класса состояния для себя. Подумайте об этом как о том, как "сворачивать свой собственный поток-статический", но с большим контролем над процессом.
  • Марширование данных по значениям между потоками вместо совместного использования одних и тех же данных. Либо сделайте свои классы передачи данных неизменяемыми, либо убедитесь, что все вызовы с поперечными потоками являются синхронными, или и то, и другое.

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

Здесь очень изобретательный пример OTT. В реальном приложении вы можете ограничить количество потоков, чтобы уменьшить накладные расходы на планирование:

  • Весь пользовательский интерфейс - один поток.
  • Фоновые вычисления - один поток.
  • Ошибка регистрации в файле диска - один поток.
  • Вызов веб-службы - один поток на уникальный физический узел.
  • Запрос базы данных - один поток на независимую группу таблиц, нуждающихся в обновлении.

Вместо того, чтобы угадывать, как выполнять задачи, профиль вашего приложения и изолировать те биты, которые (а) очень медленные, и (б) можно выполнить асинхронно. Это хорошие кандидаты для отдельного потока.

И вот чего вам следует избегать:

  • Calcs, хиты базы данных, вызовы служб и т.д. - все в одном потоке, но несколько раз наращиваются "для повышения производительности".

Ответ 10

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

EDIT: несколько заметок о создании потоков и использовании пула потоков (специфичный для .NET)

Как правило, попробуйте использовать пул потоков. Исключения:

  • Долгосрочные задачи с привязкой к ЦП и задачи блокировки не идеальны для пула потоков, потому что они заставят пул создавать дополнительные потоки.
  • Все потоки нитей потока - это фоновые потоки, поэтому, если вам нужен ваш поток на переднем плане, вы должны запустить его самостоятельно.
  • Если вам нужен поток с различным приоритетом.
  • Если вашему потоку требуется больше (или меньше), чем стандартное пространство стека 1 МБ.
  • Если вам нужно контролировать время жизни потока.
  • Если вам нужно другое поведение для создания потоков, чем предложение пула потоков (например, пул будет дросселировать создание новых потоков, что может быть или не быть тем, что вы хотите).

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

Ответ 11

Важной вещью, которую нужно заботиться (с несколькими ядрами и процессорами), является когерентность кэша.

Ответ 12

Я использую новую обнаруженную информацию о потоке везде

[Акцент добавлен]

DO помните, что небольшое знание опасно. Знание API-интерфейсов для потоковой обработки вашей платформы - легкий бит. Знать, почему и когда вам нужно использовать синхронизацию, является трудной частью. Если вы читаете "тупики", "условия гонки", "инверсия приоритета", вы начнете понимать, почему.

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

Ответ 13

Я удивляюсь, что никто еще не указал на Herb Sutter Эффективные столбцы Concurrency. На мой взгляд, это необходимо прочитать, если вы хотите идти где угодно рядом с потоками.

Ответ 14

a) Всегда создавайте только 1 поток, ответственный за ресурс ресурса. Таким образом, поток A не удалит поток ресурсов B, если B имеет право собственности на ресурс

b) Ожидайте неожиданного

Ответ 15

ДУ подумайте о том, как вы проверите свой код и отложите для этого много времени. Модульные тесты становятся более сложными. Возможно, вы не сможете вручную протестировать свой код - по крайней мере, не надежно.

Снова подумайте о времени жизни потока и о том, как потоки будут завершены. Не убивайте нити. Предоставьте механизм, чтобы они изящно выходили.

ДОПОЛНИТЕ ДОПОЛНИТЕЛЬНУЮ ДОРОЖКУ, чтобы вы могли видеть, что ваши потоки ведут себя корректно как в процессе разработки, так и в процессе производства, когда что-то ломается.

Используйте хорошую библиотеку для обработки потоков, а не для развертывания собственного решения (если можете). Например. java.util.concurrency

НЕ допускайте, чтобы общий ресурс был потокобезопасным.

НЕ ДЕЛАЙТЕ ЭТО. Например. используйте контейнер приложения, который может заботиться о проблемах с потоками. Используйте обмен сообщениями.

Ответ 16

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

Существует способ, который заключается в использовании метода Control.Invoke для обновления элемента управления в другом потоке, но он не является на 100% очевидным в первый раз!

Ответ 17

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

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

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

Ответ 18

Не обманывайтесь, думая, что вы понимаете трудности concurrency, пока не разделите голову на настоящий проект.

Все примеры блокировок, livelocks, синхронизации и т.д. кажутся простыми, и они есть. Но они будут вводить вас в заблуждение, потому что "трудность" в реализации concurrency, о которой все говорят, это когда она используется в реальном проекте, где вы не контролируете все.