1. Вот есть стандартное решение — типа как в С++. Вы запускаете поток и как бы просто исполнение раздвоилось. Теперь нужно помнить, что к одной и той же переменной могут одновременно получит доступ два разных потока — как-то синхронизировать эти доступы, учитывать это.
У меня на адаптацию мозга к пониманию многопоточности ушло несколько месяцев. Поначалу я не знал как это вообще возможно представить — ведь поток мыслей у меня один, а программа может исполняться не только в два потока но поболее...
2. А ведь есть и иные решения — типа как в Dart. Доступ к переменным всегда из одного потока — нет возможности раздвоить как в C++. Т.е. ты можешь не беспокоиться о том, что кто-то левый изменит переменную и произойдет конфликт. 100% гарантия безопасности.
Если же вам в этом подходе захотелось запустить отдельный поток — то он имеет все свои значения переменных, включая статические. Т.е. с первым потоком оно никак не соотносится. По сути как будто запустился второй экземпляр программы. Общение между ними — через спец. механизм — как будто вы общаетесь с удаленным сервером.
Подход 2 — не требует адаптации мозга и воспринимается интуитивно. Но тоже есть беда — некоторые вещи допустимы только из основного потока. В таком случае вам нужно делать некий спец. вызов — перегонку туда-обратно чисто из-за данной специфики
S>1. Вот есть стандартное решение — типа как в С++. Вы запускаете поток и как бы просто исполнение раздвоилось. Теперь нужно помнить, что к одной и той же переменной могут одновременно получит доступ два разных потока — как-то синхронизировать эти доступы, учитывать это.
Я бы не сказал что это 'как в С++'. Это скорее просто подход без абстракций, максимально близко к тому как это реализуется операционной системой.
А абстракции есть и для С++ — https://learn.microsoft.com/ru-ru/cpp/parallel/openmp/reference/openmp-directives?view=msvc-170 просто про них мало говорят
Как много веселых ребят, и все делают велосипед...
Здравствуйте, Shmj, Вы писали:
S>Какой подход вам кажется более удобным и почему? https://dlang.org/library/std/concurrency.html
обмен сообщениями т.к. решает все проблемы. в том числе когнитивные.
Здравствуйте, Shmj, Вы писали:
S>1. Вот есть стандартное решение — типа как в С++. Вы запускаете поток и как бы просто исполнение раздвоилось. Теперь нужно помнить, что к одной и той же переменной могут одновременно получит доступ два разных потока — как-то синхронизировать эти доступы, учитывать это.
В плюсах по разному можно. Вот недавно обсуждали
S>Теперь нужно помнить, что к одной и той же переменной могут одновременно получит доступ два разных потока — как-то синхронизировать эти доступы, учитывать это.
S>Dart. Доступ к переменным всегда из одного потока — нет возможности раздвоить как в C++. Т.е. ты можешь не беспокоиться о том, что кто-то левый изменит переменную и произойдет конфликт. 100% гарантия безопасности.
если нет в явном виде необходимости заботиться о "синхронизации этих доступов", это ещё не значит, что это не происходит где-то под капотом. А тогда какая разница между этими подходами? Только в том, что в одном ты ручками организуешь доступ, а в другом это делает за тебя либа/среда? Так это типовая разница между "высокоуровневым" и "низкоуровневым". В одном случае больше возможностей ценой возможных ошибок, в другом больше комфорта ценой возможно меньшей производительности и всяческих ограничений.
Здравствуйте, Shmj, Вы писали:
S>Какой подход вам кажется более удобным и почему?
Вообще странный вопрос.
Три подхода к конкурентной работе есть:
1) Атомарность и lock-free алгоритмы на её базе. Минимальные затраты и максимальные "приседания".
2) Блокировки общих данных. Потенциальные взаимоблокировки при низкой гранулярнсти и системные вызовы для остановки потока
3) Message passing — самый простой с точки зрения разработчика, но требуются хорошие библиотеки и средства языка. А также message passing это копироване данных, что не всегда приемлемо.
Нет подхода который однозначно лучше или хуже, все зависит от конкретной задачи.
ИМХО языки должны позволять все три.
Re[2]: Про варианты многопоточного взаимодействия...
Здравствуйте, gandjustas, Вы писали:
S>>Какой подход вам кажется более удобным и почему?
G>Вообще странный вопрос. G>Три подхода к конкурентной работе есть: G>1) Атомарность и lock-free алгоритмы на её базе. Минимальные затраты и максимальные "приседания". G>2) Блокировки общих данных. Потенциальные взаимоблокировки при низкой гранулярнсти и системные вызовы для остановки потока G>3) Message passing — самый простой с точки зрения разработчика, но требуются хорошие библиотеки и средства языка. А также message passing это копироване данных, что не всегда приемлемо.
G>Нет подхода который однозначно лучше или хуже, все зависит от конкретной задачи. G>ИМХО языки должны позволять все три.
Здравствуйте, Shmj, Вы писали:
S>2. А ведь есть и иные решения — типа как в Dart. Доступ к переменным всегда из одного потока — нет возможности раздвоить как в C++. Т.е. ты можешь не беспокоиться о том, что кто-то левый изменит переменную и произойдет конфликт. 100% гарантия безопасности.
Память фундаментально устроена так, что доступ из разных потоков надо искусственно ограничивать. И либо это будут ненадёжные и легко обходимые ограничения, либо перфоманс пострадает. Чудес ведь не бывает.
Здравствуйте, gandjustas, Вы писали:
S>> Ну еще есть ThreadLocal S>>и AsyncLocal
G>Они решают ровно обратную задачу — чтобы потоки НЕ взаимодействовали.
Ну это один из способов синхронизации, когда данные должны быть привязаны к потоку или задаче. Часто нужно прокинуть данные по потоку не в переменных методов, а просто в данных потока.
А так придется городить какую то хэш таблицу с привязкой данных к Id потоку, удалять по завершении и тд. И при этом удалять данные из словаря нужно синхронизировать.
Это всевозможные HTTPContex итд
Один из способ уменьшить конкуренцию. Кстати словарь проще разделить на n-словарей
Доступ к каждому через HashCode % n. Так мы уменьшим конкуренцию в n раз.
и солнце б утром не вставало, когда бы не было меня
* Когда один поток пишет в ту же ячейку, откуда другой в то-же время читает
Верно?
* Когда оба пишут в одну ячейку
Верно?
* А будет ли гонка, когда все только читают?
С железом, где это причина для гонки, то наверно лучше копировать для каждого thread
--
С точки зрения статиков.
Разделим статики на две группы:
1: readonly
2: mutable
1) Если взять только readonly статики, и дать к ним доступ из разных потоков без синхронизации, то будет ли data race?
Тут есть нюанс. Насколько readonly есть deep ?
Скажем есть const obj, а в нём mutable .prop — то как быть?
Для const-full-depth вроде понятно.
2) подРазвилка для mutable:
2.а)
скопировать mutable статики до запуска каждого нового thread
плюсы: быстрее к ним доступ, проще код, отсутсвтие deadlock -ов
минусы: занимает больше памяти; рассинхрон по значением после изменений; время будет потрачено на копирование;
2.б)
Отдельный thread работает с mutable статиками через сообщения
Механизм сообщений между потоками мне неизвестен.
Сколько байт можно передать?
Нужно ли знать все типы хранимых данных?
2.б.Х) А если сама структура типа является mutable прямо в run-time? -- Как быть с другими потоками, которые работают с mutable типом?
По идее в значении хранить указатель на версию _текущего_ типа // для readonly типа — можно и не хранить
При динамиеском изменении структуры типа — делать COW типа. Так уже имеющиеся значения не потеряют целостность.
Вроде бы не должны... (привет, методы)
А новые значения по новой версии типа — сравнимы ли со старыми?
--
Какой подход вам кажется более удобным и почему?
Предполагаемый ответ:
Если не отказываться от mutable статиков, то желательно иметь возможность задать способ хранения и доступа в каждом конкретном случае явно.
p/s: Какие ещё способы можно найти, кроме этих двух?
Здравствуйте, Sm0ke, Вы писали:
S>А можно ли эту задачу решить на уровне железа? S>Спроектировать такую архетектуру, в которой не будет проблемы data race вообще?
Ну например, можно собрать машинку из N самостоятельных машинок, на каждой из которых исполняется ровно один поток, и которые общаются сообщениями по какой-нибудь аппаратной шине, напоминающей сеть.
Re[2]: Про варианты многопоточного взаимодействия..
S>А можно ли эту задачу решить на уровне железа? S>Спроектировать такую архетектуру, в которой не будет проблемы data race вообще? S>Просто будет RW память, которой всё равно не кол-во запущенных threads
Так памяти то вобщемто и так все равно с большего
Как много веселых ребят, и все делают велосипед...
Re[5]: Про варианты многопоточного взаимодействия...
Здравствуйте, Serginio1, Вы писали:
S>А так придется городить какую то хэш таблицу с привязкой данных к Id потоку, удалять по завершении и тд. И при этом удалять данные из словаря нужно синхронизировать.
Здравствуйте, AleksandrN, Вы писали:
AN>Здравствуйте, Serginio1, Вы писали:
S>>А так придется городить какую то хэш таблицу с привязкой данных к Id потоку, удалять по завершении и тд. И при этом удалять данные из словаря нужно синхронизировать.
AN>Можно не делать самому, а использовать средства ОС — Thread Local Storage в Windows или thread key в UNIX.
AN>Но лучше использовать средства языка, если такая возможность там предусмотрена.
Здравствуйте, Shmj, Вы писали: S>Доступ к переменным всегда из одного потока — нет возможности раздвоить как в C++. Т.е. ты можешь не беспокоиться о том, что кто-то левый изменит переменную и произойдет конфликт. 100% гарантия безопасности.
А теперь возьми задачу, которая требует максимальной производительности... САУ, сводится к перемножению матриц. Это в чем суперкомпьютеры состязаются по производительнсти. Перемножение матриц это туча операций и записи конкурирующих потоков в одну ячейку, это скорее уже изученный этап, но оптиимзаци там будет до бесконечности.
Вроде как просто — перемножить матрицы, но когда у тебя N ядер, и еще скорость зависит от обращений в память, а это зависит от данных в кеш — в итоге будет чет нетривиальное (Intel® Math Kernel Library (Intel® MKL)). И даже на копеечных древних процессорах крайне близко к 1 TFLOPS (800 GFLOPS выдает один Xeon 2696v4 — можете свои процы затестить — версия древняя, но все еще рабочая AVX2 https://github.com/sanekgusev/LinX-old/releases/tag/0.6.5, но такие процессоры стаятся парой и запросто преодолевают рубеж в 1 TFLOPS). Хочешь многопоток, много процов и дичайшую производительность ну чуть хуже ThreadRipper, Китайцы распродают по запчастям свои устаревшие суперкомпьютеры. ЭЭЭ, 18 ядерный 2696v3 стоит 4тыс. руб? и он ставится парой, и тогда будет дикое > 1 TFLOPS.
Современные десктоп процы (i9) уже тоже перешагнули за 1 TFLOPS, ну а суперкомпьютеры там на PFLOPS идут.
Ествественно там вся логика рассчитана, а не только многопоток, все нюансы — как кэш работает. В реальных математических задачах показать производительность примерно равную теоретически заложенной в железке — тупо количество ядер помножить на чатоту и число операций за такт. Если это сходится с заявленным для процессора и такие вычислительные мощности подтверждаются на практике — это радует, что процессор как заявленно работает и софт максимально опимально написан (увы, кто пишет софт сейчас скорее могут быть полные неучи в многопроцессорорных задачах, вплоть не понимают что разные потоки с разной скоростью выполняться могут и не имеют понятия, что при стыке двух потоков будут проблемы).