Почему потоки можно было бы считать “злыми”?

Я обычно иду с чем-то как реализация, данная в Josh Bloch невероятный Эффективный Java. Это быстро и создает довольно хороший хеш, который вряд ли вызовет коллизии. Выберите два различных простых числа, например, 17 и 23, и сделайте:

public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = 17;
        // Suitable nullity checks etc, of course :)
        hash = hash * 23 + field1.GetHashCode();
        hash = hash * 23 + field2.GetHashCode();
        hash = hash * 23 + field3.GetHashCode();
        return hash;
    }
}

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

Это лучше, чем обычная практика XOR хэш-коды луга по двум главным причинам. Предположим, что у нас есть тип с два int поля:

XorHash(x, x) == XorHash(y, y) == 0 for all x, y
XorHash(x, y) == XorHash(y, x) for all x, y

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

Эта страница дает довольно много опций. Я думаю для большей части вышеупомянутого случаев, "достаточно хорошо", и невероятно легко помнить и разобраться. альтернатива FNV столь же проста, но использует различные константы и XOR вместо ADD как операция объединения. Это смотрит что-то как код ниже, но нормальный алгоритм FNV воздействует на отдельные байты, таким образом, это потребовало бы, чтобы изменение выполнило одно повторение на байт, вместо на 32-разрядное значение хэш-функции. FNV также разработан для переменных длин данных, тогда как способ, которым мы используем его здесь, всегда для того же количества значений полей. Комментарии к этому ответу предполагают, что код здесь на самом деле не работает также (в демонстрационном протестированном случае) как дополнительный подход выше.

// Note: Not quite FNV!
public override int GetHashCode()
{
    unchecked // Overflow is fine, just wrap
    {
        int hash = (int) 2166136261;
        // Suitable nullity checks etc, of course :)
        hash = (hash * 16777619) ^ field1.GetHashCode();
        hash = (hash * 16777619) ^ field2.GetHashCode();
        hash = (hash * 16777619) ^ field3.GetHashCode();
        return hash;
    }
}

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

Согласно документация :

можно переопределить GetHashCode для неизменных ссылочных типов. В целом, для изменяемых ссылочных типов, необходимо переопределить GetHashCode только если:

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

23
задан Shog9 28 July 2009 в 02:03
поделиться

9 ответов

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

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

14
ответ дан 29 November 2019 в 01:23
поделиться

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

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

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

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

12
ответ дан 29 November 2019 в 01:23
поделиться

Да, вроде того. Sunbird (календарь Mozilla с открытым исходным кодом) основан на sqlite, и я только что загрузил и разархивировал их исходный код. В нем есть файлы .sql.

ftp://ftp.mozilla.org/pub/mozilla.org/calendar/sunbird/releases/0.9/source/

mozilla \ calendar \ provider \ storage \ schema- 7.sql - это схема, которую sunbird использует для создания корректных файлов iCal, так что это не может быть так уж плохо.

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

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

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

11
ответ дан 29 November 2019 в 01:23
поделиться

Я бы интерпретировал это иначе. Дело не в том, что потоков злы, а в том, что побочные эффекты злы в многопоточном контексте (что гораздо менее привлекательно).

Побочный эффект в этом контексте это то, что влияет на состояние, совместно используемое более чем одним потоком, будь оно глобальным или просто общим. Недавно я написал обзор 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, C #) также поощряют использование неизменяемых объектов (неизменяемый объект - это объект, состояние которого не может измениться после создания).

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

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

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

7
ответ дан 29 November 2019 в 01:23
поделиться

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

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

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

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

4
ответ дан 29 November 2019 в 01:23
поделиться

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

Еще один способ «зла» потоков заключается в том, что большая часть кода фреймворка является не предназначен для работы с несколькими потоками, поэтому вам придется управлять своим собственным механизмом блокировки для этих структур данных.

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

1
ответ дан 29 November 2019 в 01:23
поделиться

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

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

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

Потоки не являются злом, они действительно могут быть очень полезными.

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

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

1
ответ дан 29 November 2019 в 01:23
поделиться

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

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

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

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

Итак, да,

0
ответ дан 29 November 2019 в 01:23
поделиться

По состоянию на 15 февраля 2010 года, похоже, в Silverlight нет встроенного способа сделать это.

Вот поток , в котором обсуждается этот вопрос .

У Рене Шульте есть пример, Снимки EdgeCam - сохранение снимков веб-камеры Silverlight 4 в JPEG , который сохраняет поток веб-камеры в виде последовательных изображений JPEG.

-121--4501495-

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

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

Обратите внимание, что это справедливо только для неизменяемого класса Точки. Если бы удалось изменить значение точки, PositivePoint и Point могли бы вообще не иметь отношения подкласса, так как операция p.x = -1 для PositivePoints не удалась бы.

Edit: Чтобы развить:

Допустим, у нас 2 размерный массив, который автоматически растет при необходимости (т.е. при прохождении двух положительных индексов вы никогда не получаете ошибку index-out-of-bounds). Теперь у нас есть функция, которая принимает positiveInteger p и затем обращается к двумерному массиву с индексом x, y. Это не может потерпеть неудачу, потому что x и y гарантированно будут положительными, и двумерный массив может быть проиндексирован с любой парой положительных индексов. Однако если точка является подтипом PositivePoint, p может иметь отрицательные значения, даже если он объявлен положительным. Это означает, что использовать массив для индексации небезопасно.

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

-121--4435113-

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

5
ответ дан 29 November 2019 в 01:23
поделиться
Другие вопросы по тегам:

Похожие вопросы: