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

Почему потоки могут считаться "злыми"?

Я читал SQLite FAQ и натолкнулся на этот отрывок:

Нити злы. Избегайте их.

Я не совсем понимаю утверждение "Нить злая". Если это так, то какова альтернатива?

Мое поверхностное понимание потоков:

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

Примечание.. Поскольку я не знаком с потоками в Windows, я надеюсь, что обсуждение будет ограничено потоками Linux/Unix.

4b9b3361

Ответ 1

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

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

Ответ 2

Простой ответ, как я его понимаю...

Большинство моделей потоков используют "shared state concurrency", что означает, что два процесса выполнения могут совместно использовать одну и ту же память одновременно. Если один поток не знает, что делает другой, он может изменять данные таким образом, чтобы другой поток не ожидал. Это вызывает ошибки.

Темы являются "злыми", потому что вам нужно объединить свои мысли вокруг потоков n, работающих одновременно в одной и той же памяти, и всех интересных вещей, которые идут с ним (тупики, гоночные условия и т.д.).

Вы можете прочитать о моделях Clojure (неизменяемых данных) и моделях Erlang (message passsing) concurrency для альтернативных идей о том, как достичь аналогичных целей.

Ответ 3

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

То есть: Учитывая тот же набор входов, однопоточная программа (в большинстве случаев) всегда будет делать то же самое.

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

Результат: отладка многопоточной программы может быть намного сложнее, чем отладка однопоточной программы, потому что, если вы не знаете, что делаете, может быть очень легко закончить условие гонки или тупик ошибка, которая появляется (по-видимому) случайным образом один или два раза в месяц. Программа будет отлично смотреться в вашем отделе QA (поскольку у них нет месяца для ее запуска), но как только он появится в поле, вы будете слышать от клиентов, что программа разбилась, и никто не может воспроизвести авария... bleah.

Подводя итог, потоки на самом деле не "злые", но они сильные juju и не должны использоваться, если (а) они вам действительно нужны, и (б) вы знаете, к чему вы входите. Если вы используете их, используйте их как можно более экономно и старайтесь сделать их поведение настолько глупым, насколько возможно. Особенно при многопоточности, если что-то может пойти не так, оно (рано или поздно) будет.

Ответ 4

Я бы это интерпретировал по-другому. Это не то, что нити являются злыми, это то, что побочные эффекты являются злыми в многопоточном контексте (что гораздо менее запоминающимся).

В этом контексте побочным эффектом является то, что влияет на состояние, разделяемое более чем одним потоком, будь то глобальное или просто разделяемое. Недавно я написал обзор Spring Batch, и один из используемых фрагментов кода:

private static Map<Long, JobExecution> executionsById = TransactionAwareProxyFactory.createTransactionalMap();
private static long currentId = 0;

public void saveJobExecution(JobExecution jobExecution) {
  Assert.isTrue(jobExecution.getId() == null);
  Long newId = currentId++;
  jobExecution.setId(newId);
  jobExecution.incrementVersion();
  executionsById.put(newId, copy(jobExecution));
}

Теперь существует не менее трех серьезных проблем с потоками в менее чем 10 строках кода. Примером побочного эффекта в этом контексте было бы обновление статической переменной currentId.

Функциональное программирование (Haskell, Scheme, Ocaml, Lisp, другие), как правило, поддерживают "чистые" функции. Чистая функция - это без побочных эффектов. Многие императивные языки (например, Java, С#) также поощряют использование неизменяемых объектов (неизменным объектом является тот, чье состояние не может измениться после создания).

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

Преимущество процессов заключается в том, что существует меньше общего состояния (обычно). В традиционном программировании UNIX C выполнение fork() для создания нового процесса приведет к общему состоянию процесса, и это использовалось как средство IPC (межпроцессная связь), но обычно это состояние заменяется (с помощью exec()) с что-то еще.

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

Ответ 5

Документ, с которым вы связаны, кажется, очень хорошо объясняет. Вы читали его?

Имейте в виду, что поток может ссылаться на конструкцию языка программирования (как на большинстве языков программирования или ООП, вы создаете поток вручную и говорите ему, чтобы он выполнял функцию), или они могут ссылаться на аппаратную конструкцию ( Каждое ядро ​​ЦП выполняет один поток за раз).

Потоки аппаратного уровня, очевидно, неизбежны, это то, как работает ЦП. Но CPU не волнует, как concurrency выражается в вашем исходном коде. Например, это не должно быть вызовом функции "beginthread". ОС и процессор просто должны сказать, какие потоки команд должны выполняться.

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

Ответ 6

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

Ответ 7

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

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

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

Недавний дрейф к ориентированному на события concurrency является отличной разработкой. Эти программы обычно имеют большую выносливость после их развертывания.

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

Ответ 8

Создание большого количества потоков без ограничений действительно является злом.. использование механизма объединения (threadpool) смягчит эту проблему.

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

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

Ответ 9

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

Хорошая вещь о них: вы можете распараллелить программу для повышения производительности. Некоторые примеры: 1) В программе редактирования изображений поток может запускать обработку фильтра независимо от GUI. 2) Некоторые алгоритмы поддаются нескольким потокам.

Что плохого в них? если программа плохо спроектирована, они могут привести к проблемам взаимоблокировки, когда оба потока ждут друг друга для доступа к одному и тому же ресурсу. А во-вторых, дизайн программы может меня усложнить из-за этого. Кроме того, некоторые библиотеки классов не поддерживают потоковую обработку. например Функция библиотеки c "strtok" не является "потокобезопасной". Другими словами, если бы два потока использовали его в то же время, они бы сбивали друг друга. К счастью, часто существуют альтернативы с потоковой безопасностью... например. повысить библиотеку.

Нитки не злые, они могут быть очень полезными.

В Linux/Unix потоковая передача в прошлом не была хорошо поддержана, хотя я полагаю, что Linux теперь поддерживает потоки Posix, а другие объединения поддерживают потоки теперь через библиотеки или изначально. т.е. pthreads.

Наиболее распространенной альтернативой потоковой обработке под платформами Linux/Unix является fork. Вилка - это просто копия программы, включающая в себя открытые дескрипторы файлов и глобальные переменные. fork() возвращает 0 дочернему процессу и идентификатор процесса родительскому. Это более старый способ делать вещи под Linux/Unix, но все еще хорошо используется. Потоки используют меньше памяти, чем вилка, и быстрее запускаются. Кроме того, взаимодействие между процессами - это больше, чем простые потоки.

Ответ 10

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

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

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

Неконтролируемые потоки могут потреблять циклы процессора, пережевывать ОЗУ, вызывать execeptions и т.д. и т.д., и единственный способ остановить их - сообщить планировщику процесса ОС принудительно завершить поток, сведя его к указателю на указатель (т.е. прекратить выполнение). Если вы принудительно сообщите CPU о прекращении выполнения последовательности инструкций, что происходит с ресурсами, которые были распределены или используются этими инструкциями? Оставлены ли они в стабильном состоянии? Правильно ли они освобождены? и т.д...

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

Ответ 11

Будучи старым инженером, я с трудом согласен с ответом Техаса Тайна.

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

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

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