Можно ли однопоточное приложение сделать многопоточным
Когда мы должны использовать потоки в нашем приложении. Другими словами, когда нужно преобразовать однопоточное приложение в многопоточное приложение.
Являясь разработчиком, я думаю, что задача, которая мешает вашему приложению работать бесперебойно. Эта задача может обрабатываться потоком. Подобно тому, как мы получаем данные GPS непрерывно.
Я думаю, есть еще несколько причин для создания потока в вашем приложении. Пожалуйста, поделитесь своим мнением.
Причины, по которым я могу думать о моей голове (и я уверен, что их больше):
1. Разгрузка партии работы на рабочий поток, чтобы ваша программа могла либо продолжать отвечать на ввод пользователя, либо чтобы вы могли продолжать работать с другим кодом, который не полагается на эту работу.
2. Обработка ввода/вывода, в частности, серверной и сетевой связи, когда время ответа является переменным или неизвестным.
3. Параллельная обработка данных, в которых вы можете разделить работу на дискретные независящие единицы работы
4. Работа с таймером i.e "каждые 500 мс проверяет, изменился ли x"
Однако переключение на многопоточное или параллельное программирование не обойдется без ошибок, особенно если эти потоки должны получить доступ к общим данным, следовательно, количество вопросов на SO о мьютексы и синхронизация потоков!
ответил(а) 2010-01-30T21:47:00+03:00 11 лет, 10 месяцев назадОчень хорошее использование потоков - это разобрать различные проблемные пространства. Например, если один поток выполняет удаленную связь, взаимодействие с пользователем и вычисления, то по мере развития программы может возникнуть несколько проблем. Пользовательский интерфейс может измениться по мере замедления связи или увеличения ваших расчётов в потреблении процессора.
Разделяя коммуникационные, пользовательские и алгоритмические части вашего кода (короткий список, особенно если вы делаете системы реального времени) в свои собственные потоки, вы увеличиваете модульность и понятность кода, вы уменьшаете вероятность из-за непредвиденных последствий, и вы получаете контроль над приоритетом выполнения и синхронизацией.
Конечно, многопоточность действительно имеет свои проблемы, особенно условия гонки и вещи, происходящие из ожидаемой последовательности, но они управляемы с помощью семафора, мьютексов и т.п.
ответил(а) 2010-01-30T21:40:00+03:00 11 лет, 10 месяцев назадОчень полезно рассуждать о том, что "конвертировать" в том, что машины с несколькими ядрами становятся нормой, и довольно грустно видеть, что старые программы плохо работают, потому что они работают только на одном ядре.
У меня было число хрустящее приложение, где одна часть была очень медленной на Mac с 16 виртуальными ядрами для необходимого (и часто называемого) алгоритма сортировки, работала только на одном ядре. Я реализовал свой собственный многопоточный алгоритм сортировки, чтобы адаптироваться к количеству ядер и бинго, удивительно ускорить процесс ускорения.
Такое очень неоптимизированное поведение можно четко увидеть с помощью монитора ЦП.
Итак, один ответ на ваш вопрос "когда мы должны использовать поток?" просто "когда вы искусственно замедляете свое программное обеспечение, заставляя его работать только на одном ядре".
В качестве побочного примечания это очень интересно, потому что в этом примере сложность алгоритма сортировки остается O (n log n), но эффект "распараллеливания" дает потрясающее ускорение скорости.
Другие причины могут заключаться в том, что в некоторых случаях правильная многопоточность вашего приложения, например, с использованием хорошо продуманной схемы procuder/consumer, также может помочь уменьшить некоторые разногласия в ресурсах.
Название говорит само за себя: есть ли способ получить более старую программу, предназначенную для использования одного ядра ЦП для использования нескольких ядер ЦП?
Что если бы существовала программа, которая «эмулировала» процессор (например, vm?), Но взяла ваш многоядерный процессор и эмулировала одноядерный процессор с гораздо большей вычислительной мощностью на поток? Это возможно? Кто будет управлять синхронизацией между несколькими потоками, потому что порядок имеет значение.К сожалению, устаревшая программа, написанная для одного процессора, не может быть принудительно использована для нескольких ядер процессора. Использование нескольких ядер ЦП требует нескольких потоков, которые должны обмениваться данными друг с другом, гарантируя, что условия гонки и другие проблемы не возникнут. Более старое приложение нельзя заставить использовать больше, чем ядро ЦП, если оно не переписано для этого, и только если природа приложения позволяет его распараллеливать.
Стоит также отметить, что некоторые приложения просто не могут быть переписаны с учетом нескольких ядер. Это в основном зависит от того, насколько распараллелен процесс. Простым примером непараллелизуемого процесса может быть репродукция человека: одна женщина может родить одного ребенка за 9 месяцев. Девять женщин могут сделать 9 детей за 9 месяцев, но вы не можете использовать 9 женщин, чтобы родить одного ребенка за один месяц. В принципе, можно написать программу для анализа другой программы и попытки ее распараллеливания. Тем не менее, даже очень ограниченные «легкие» версии этой проблемы генерируют докторскую степень. диссертации за последние пару поколений и прогресс был медленным. Полная проблема вполне может быть ИИ-полной. Отлично, спасибо вам всем, вы привели меня в ярость из-за вашего примера «Женщины и рождение», особенно когда вы говорите о производстве: D Что делать, если в программе есть куча независимых математических операций, которые позже используются для друг друга? Может быть, второй поток может разобрать программу, хм, я не знаю, что я говорю. Если поток может анализировать сборку .. «Выполните дорогостоящую операцию здесь, затем выполните вторую дорогостоящую операцию с этим, которая не затрагивает первую дорогостоящую операцию». Хорошо, я положу одну дорогую операцию на один поток, одну на другой, затем синхронизирую их назад в основной поток. Я предполагаю, что целая программа должна была бы быть предварительно проанализирована, чтобы идентифицировать независимые дорогостоящие операции.Какова ваша цель с этим? Увеличение производительности? К сожалению, приложения, предназначенные для использования только 1 ядра, не будут использовать больше. Вот о чем этот разговор о «многопоточных» приложениях.
Существует как минимум три метода использования нескольких процессоров в программе, предназначенной для использования одного ядра. Наиболее простым из этих методов является использование библиотек и системного кода, который использует несколько ядер или может выполняться хотя бы частично параллельно с кодом приложения. Сборка мусора является примером функциональности, которая может быть распараллелена и может выполняться параллельно с выполнением приложения. Даже без автоматического управления памятью существует некоторый потенциал для параллелизма в функциях освобождения памяти, потому что распределителю памяти может потребоваться некоторая работа, помимо простой маркировки секции памяти как доступной.
Второй метод - бинарный перевод. Хотя это можно считать «переписыванием приложения», это делается программным обеспечением и без доступа к исходному коду. Создание параллелизма на уровне потоков, по-видимому, не было главной целью большинства исследований и разработок с использованием бинарной трансляции (что часто касается запуска унаследованного кода на другом ISA, использования расширений ISA или оптимизации для конкретной микроархитектуры и использования динамической информации для обеспечения более высокой производительности). качественная профильная оптимизация), но потенциал очевиден.
Третий метод - умозрительная многопоточность. В настоящее время ни один из известных мне процессоров не поддерживает спекулятивную многопоточность с аппаратным управлением. Тем не менее, с введением аппаратной транзакционной памяти, реализация системы во время выполнения делает это несколько более практичным, поскольку HTM может использоваться для обнаружения конфликтов в использовании памяти. Управляемая программным обеспечением спекулятивная многопоточность обычно включает в себя некоторую двоичную трансляцию, но ее спекулятивный характер оправдывает рассмотрение отдельного метода.
Практичность этих методов ограничена затратами, связанными с существующими системами (включая стоимость связи между потоками и порождаемыми потоками), ограниченным параллелизмом, который они могут использовать, и ограниченным возвратом инвестиций (важные приложения, которые могут быть выгодно распараллеливание, вероятно, будет переписано, многие приложения получат относительно небольшую выгоду, если вообще будут пользоваться такими методами (особенно с учетом ограничений мощности / температуры, позволяющих одному ядру работать с более высокой частотой, чем у нескольких ядер), а затраты на разработку значительны). Тем не менее , эти методы делают существование и теоретически возможно использовать несколько ядра с приложением , предназначенным для использования одного ядра.
Нет, этого не может быть. Программа явно написана для использования нескольких ядер. Это не тривиально, чтобы сделать работу в нескольких ядрах. Требует синхронизации всех потоков. Как один бросает мяч, другой ловит, один полирует мяч, один чистит, другой проверяет, достаточно ли воздуха в нем. Теперь представьте, что каждый персонаж работает в потоке независимо друг от друга. Кто-то пытается поймать мяч, когда его не бросили. Или кто-то пытается отполировать мяч во время игры. Или два персонажа, пытающиеся поймать и бросить мяч одновременно. Есть так много способов сбой. Поэтому программисты должны тщательно перепроектировать одноядерное приложение, чтобы оно могло использовать преимущества нескольких ядер.
Начнем с того, что код потока, как и код процесса, обычно содержит несколько процедур. У этих процедур могут быть локальные и глобальные переменные, а также параметры. Локальные переменные и параметры проблем не создают, проблемы возникают с теми переменными, которые носят глобальный характер для потока, но не для всей программы. Глобальность этих переменных заключается в том, что их использует множество процедур внутри потока (поскольку они могут использовать любую глобальную переменную), но другие потоки логически должны их оставить в покое.
Рассмотрим в качестве примера переменную errno, поддерживаемую UNIX. Когда процесс (или поток) осуществляет системный вызов, терпящий неудачу, код ошибки помещается в errno. На рис. 2.13 поток 1 выполняет системный вызов access, чтобы определить, разрешен ли доступ к конкретному файлу.
Операционная система возвращает ответ в глобальной переменной errno. После возвращения управления потоку 1, но перед тем, как он получает возможность прочитать значение errno, планировщик решает, что поток 1 на данный момент времени вполне достаточно использовал время центрального процессора и следует переключиться на выполнение потока 2. Поток 2 выполняет вызов open, который терпит неудачу, что вызывает переписывание значения переменной errno, и код access первого потока утрачивается навсегда. Когда чуть позже возобновится выполнение потока 1, он считает неверное значение и поведет себя некорректно.Существуют разные способы решения этой проблемы. Можно вообще запретить использование глобальных переменных. Какой бы заманчивой ни была эта идея, она вступает в конфликт со многими существующими программами.
чается в назначении каждому потоку собственных глобальных переменных (рис. 2.14). В этом случае у каждого потока есть своя закрытая копия еггпо и других глобальных переменных, позволяющая избежать возникновения конфликтов. В результате такого решения создается новый уровень области определения, где переменные видны всем процедурам потока (но не видны другим потокам), вдобавок к уже существующим областям определений, где переменные видны только одной процедуре и где переменные видны из любого места программы.
Однако доступ к закрытым глобальным переменным несколько затруднен, поскольку большинство языков программирования имеют способ выражения локальных и глобальных переменных, но не содержат промежуточных форм. Есть возможность распределить часть памяти для глобальных переменных и передать ее каждой процедуре потока в качестве дополнительного параметра. Решение не самое изящное, но работоспособное.
В качестве альтернативы можно ввести новые библиотечные процедуры для создания, установки и чтения глобальных переменных, видимых только внутри потока. Первый вызов процедуры может иметь следующий вид:
Он выделяет хранилище для указателя по имени bufptr в динамически распределяемой области памяти или в специальной области памяти, зарезервированной для вызывающего потока. Независимо от того, где именно размещено хранилище, к глобальным переменным имеет доступ только вызывающий поток. Если другой поток создает глобальную переменную с таким же именем, она получает другое место хранения и не конфликтует с уже существующей переменной.
Для доступа к глобальным переменным нужны два вызова: один для записи, а другой для чтения. Процедура для записи может иметь следующий вид:
Она сохраняет значение указателя в хранилище, ранее созданном вызовом процедуры create global.
Процедура для чтения глобальной переменной может иметь следующий вид:Она возвращает адрес для доступа к данным, хранящимся в глобальной переменной.
Подобная проблема возникает и с процедурами распределения памяти, к примеру с процедурой malloc в UNIX, работающей с весьма важными таблицами использования памяти, например со связанным списком доступных участков памяти. Когда процедура malloc занята обновлением этих списков, они могут временно пребывать в несообразном состоянии, с указателями, которые указывают в никуда. Если в момент такого несообразного состояния произойдет переключение потоков и из другого потока поступит новый вызов, будут использованы неверные указатели, что приведет к сбою программы. Для эффективного устранения всех этих проблем потребуется переписать всю библиотеку. А это далеко не самое простое занятие с реальной возможностью внесения труднообнаруживаемых ошибок.
Другим решением может стать предоставление каждой процедуре оболочки, которая устанавливает бит, отмечающий, что библиотека уже используется. Любая попытка другого потока воспользоваться библиотечной процедурой до завершения предыдущего вызова будет заблокирована. Хотя этот подход вполне осуществим, он существенно снижает потенциальную возможность параллельных вычислений.
А теперь рассмотрим сигналы. Некоторые сигналы по своей логике имеют отношение к потокам, а некоторые не имеют к ним никакого отношения. К примеру, если поток
Остается еще одна проблема, создаваемая потоками, — управление стеком. Во многих системах при переполнении стека процесса ядро автоматически предоставляет ему дополнительное пространство памяти. Когда у процесса несколько потоков, у него должно быть и несколько стеков. Если ядро ничего не знает о существовании этих стеков, оно не может автоматически наращивать их пространство при ошибке стека. Фактически оно даже не сможет понять, что ошибка памяти связана с разрастанием стека какого-нибудь потока.
Разумеется, эти проблемы не являются непреодолимыми, но они наглядно демонстрируют, что простое введение потоков в существующую систему без существенной доработки приведет к ее полной неработоспособности. Возможно, необходимый минимум будет состоять в переопределении семантики системных вызовов и переписывании библиотек. И все это должно быть сделано так, чтобы сохранялась обратная совместимость с существующими программами, когда все ограничивается процессом, имеющим только один поток. Дополнительную информацию о потоках можно найти в трудах Хаузера (Hauser et al., 1993), Марша (Marsh et al., 1991) и Родригеса (Rodrigues et al., 2010).
'> Однопоточное приложение перенести на многопоточное (прил. простое) , VS 2008 console applicationстараюсь сделать так:
1) ввести несколько потоков (в данном случае 3, т к ТРИ входных фаила)
2) в каждом потоке считывать информацию и делать еи анализ, т е вести параллельную обработку фаилов
3) завершить программу, когда все фаилы будут обработаны
поскольку только начал изучать, то разбегаются глаза, какие функции использовать из множества для работы с Тредами и Процессами. Понятно, что нужно использовать createThread, а вот нужен ли createProcess (главныи поток или он как бы уже создан во время запуска приложения)..
Также хотелось бы применить WaitForMultipleObjects. Также потом планирую разобраться с приоритетом потоков и немного переписать многопоточныи вариант.
Подскажите плиз алгоритм по даннои задаче, что лучше задеиствовать, какие будут проблемы при синхронизации. какие объекты ядра лучше задеиствовать(мьютексы, события, семафоры).
подскажите как быть то. буду признателен.
Понятно, что нужно использовать createThread, а вот нужен ли createProcess (главныи поток или он как бы уже создан во время запуска приложения)..CreateProcess создает именно процесс, а не только главный поток(который он тоже создает). CreateProcess создает и инициализирует новый процесс(соответствующий объект ядра, выделяет процессу адресное пространство и т.д.) и главный поток нового процесса. В твоем случае вызывать не нужно.
Кстати, тебе нужно в раздел WinAPI
popsa, именно нужно было INFINITE применить, т е ждать до бесконечности.
твои вариант хорош. буду разбираться.
кстати, а что конкретно ты подразумеваешь под синхронизациеи вывода.
в общем реализовал по твоему алгоритму задачу..все прекрасно работает. в смысле никаких сбоев.
касательно синхронизации вывода, мне кажется, я понял что имеется ввиду.
у меня операторы в программе другие, но вывод также имеется:
как видно из данного оператора, печатается некии номер (из диапазона от 1 до 10) и СРАЗУ происходит переход на новую строку.
когда происходит однопоточная обработка, т е всегда четко:
а когда многопоточная, получается ситуация, от которои челюсть отвисла.
иногда выкидывает числа на ОДНОИ строке, т е не редкость видеть такое:
долго думал как такое возможно. получается что разные потоки в примерно в одно и то же время (с точностью до кванта) обращаются к строке: cout << i++ << endl;
и например в 5 потоке произошел вывод(cout << i++), но endl еще не успел отработать(думал что это атомарная операция, как транзакция + ко всему), параллельно 6 поток также начал печать i++ и получается "наложение" двух выводов, т е ни один из endl - ов не успел отработать. комично.
Понял, чтобы увеличить скорость выполнения программы, можно "играть" приоритетами создаваемых потоков, и можно воспользоваться функцией SetThreadPriority.
подскажите плиз как реализовать задуманное. буду очень признателен.
P.S. работаю с Borland C++ 5.0. Нашел в интернет, что имеются некие утилиты, pview, помогащие управлять приоритетами, или нечто подобное, правда еще не понял как ими пользоваться.
Добавлено 19.12.09, 22:46
P.S.S. на однопоточнои обработке время выполнения программы занимает приблизительно 9.76 сек, а когда многопоточная, то 5.02. хм. разница более чем существенна. интересно, как же повлияют приоритеты.
"Играть" можно, но только на быстродействии это врядли скажется
понятно, но теория обещает поднять производительность на 30% по сравнению с простои многопоточностью.
а можно какои нибудь пример. (еще не понятно у какого потока из 10, например, увеличивать приоритет, или у всех сразу нужно?).
понятно, но теория обещает поднять производительность на 30% по сравнению с простои многопоточностью.Добавлено 20.12.09, 10:42
PS: При обработке дост.больших файлов вводить многопоточность нужно с умом, имея в виду то, что перемещение головок диска при попеременном чтении разных файлов осуществляется достаточно долго - порядка 10мс, и за это, "потраченное впустую" время можно было бы прочитать порядка 500Кб и более данных из одного файла при современных скоростях последовательного чтения 50Мб/с и более.
leo, ну вы просто ФАНТАСТИЧЕСКИ описали мою задачу. почти все по ТЗ. у вас интуция и логика поставлены на 100% каждая.
вот мои полныи исходник(порядка 300 строк кода), правда я вслепую указал приоретет. Leo, Qraizer, D_KEY: Подскажите пожалуиста как лучше сделать с SetThreadPriority, буду преочень признателен:
Читайте также: