Записки программиста
Пример простейшей многопоточной программы на WinAPI
25 декабря 2013
Продолжаем вспоминать Windows API. До сих пор мы писали грустные и унылые однопоточные программки из серии «вызови правильную процедуру с нужными аргументами и посмотри на ее код возврата». Сегодня же мы наконец-то напишем хоть и простенькую, но все же самую что ни на есть многопоточную программу, с теми самыми тредами и мьютексами, которые все постоянно ругают.
Из новых процедур нам понадобятся следующие.
CreateThread ( NULL , 0 , hMutex , 0 , NULL ) ;
Как нетрудно догадаться, CreateThread создает новую нитку. Аргументы слева направо — (1) нечто, о чем сейчас знать не нужно, (2) размер стека в байтах, округляется до размера страницы, если ноль, то берется размер по умолчанию, (3) указатель на процедуру, с которой следует начать выполнение потока, (4) аргумент процедуры, переданной предыдущим аргументом, обычно здесь передается указатель на некую структуру, (5) флаги, например, можно создать приостановленный поток (CREATE_SUSPENDED), а затем запустить его с помощью ResumeThread, (6) куда записать ThreadId созданного потока. В случае успеха процедура возвращает хэндл созданного потока. В случае ошибки возвращается NULL, а подробности можно узнать через GetLastError.
Стандарт POSIX
CreateMutex ( NULL , FALSE , NULL ) ;
Процедура CreateMutex создает новый мьютекс. О первом аргументе сейчас знать не нужно. Если второй аргумент равен TRUE, созданный мьютекс будет сразу залочен текущим потоком, если же второй аргумент равен FALSE, создается разлоченный мьютекс. Третий аргумент задает имя мьютекса, если мы хотим создать именованный мьютекс.
Именованные мьютексы нам пока что не понадобятся, поэтому передаем NULL. Возвращаемые значения в точности такие же, как и у CreateThread.
WaitForSingleObject ( hHandle , dwTimeout ) ;
WaitForSingleObject ждет, когда объект, хэндл которого был передан первым аргументом, перейдет в сигнальное состояние (signaled state). Кроме того, в зависимости от типа объекта, процедура может менять его состояние. WaitForSingleObject может быть применен к мьютексам, семафорам, потокам, процессам и не только.
Если hHandle представляет собой хэндл мьютекса, процедура ждет, когда мьютекс освободится, а затем лочит его. Если же hHandle является хэндлом потока, то процедура просто ждет его завершения. Второй аргумент задает время ожидания в миллисекундах. Можно ждать вечно, передав специальное значение INFINITE. Если указать ноль, процедура не переходит в режим ожидания, а возвращает управление немедленно.
В случае ошибки процедура возвращает WAIT_FAILED, а подробности поможет узнать GetLastError. В случае успеха возвращается WAIT_OBJECT_0, если мы дождались перехода объекта в сигнальное состояние, и WAIT_TIMEOUT, если отвалились по таймауту. Также мы можем получить WAIT_ABANDONED. Это происходит в случае, если нить, державшая мьютекс, завершилась, не освободив его. В этом случае мьютекс становится залочен текущей нитью, но целостность данных, доступ к которым ограничивался мьютексом, по понятным причинам находится под вопросом.
Процессы. Способы создания WinAPI и POSIX процессов.
WaitForMultipleObjects ( dwCount , lpHandles , bWaitAll , dwTimeout ) ;
Процедура WaitForMultipleObjects работает аналогично WaitForSingleObject, но для массива объектов. Первый аргумент задает размер массива, должен быть строго больше нуля и не превышать MAXIMUM_WAIT_OBJECTS (который равняется в точности 64). Вторым аргументом задается указатель на массив хэндлов.
В массиве могут содержаться хэндлы на объекты разных типов, но многократное включение одного и того же хэндла считается ошибкой. Если третий аргумент равен TRUE, процедура ждет перехода в сигнальное состояние всех объектов, иначе — любого из указанных объектов. Семантика последнего аргумента точно такая же, как и в случае с WaitForSingleObject.
Процедура возвращает WAIT_FAILED в случае ошибки и WAIT_TIMEOUT в случае отваливания по таймауту. Если процедура вернула значение от WAIT_OBJECT_0 до WAIT_OBJECT_0 + dwCount — 1, то в случае bWaitAll == TRUE все объекты из массива перешли в сигнальное состояние, а в случае bWaitAll == FALSE в сигнальное состояние перешел объект с индексом код возврата минус WAIT_OBJECT_0. Если процедура вернула значение от WAIT_ABANDONED_0 до WAIT_ABANDONED_0 + dwCount — 1, то в случае bWaitAll == TRUE все ожидаемые объекты перешли в сигнальное состояние и по крайней мере один из объектов оказался брошенным мьютексом, а в случае bWaitAll == FALSE брошенным оказался мьютекс с индексом код возврата минус WAIT_ABANDONED_0.
Если bWaitAll == TRUE, процедура не меняет состояния объектов до тех пор, пока все они не перейдут в сигнальное состояние. Таким образом, пока вы ждете, мьютексы могут продолжать лочиться и анлочиться другими потоками. Если bWaitAll == FALSE и сразу несколько объектов перешли в сигнальное состояние, процедура всегда работает с объектом в массиве, имеющим минимальный индекс.
ReleaseMutex ( hMutex ) ;
ReleaseMutex анлочит ранее залоченный мьютекс. В случае успеха процедура возвращает значение, отличное от нуля. В случае ошибки возвращается ноль, а подробности доступны через GetLastError. Что характерно, для предотвращения дэдлоков Windows позволяет потокам лочить один и тот же мьютекс несколько раз, но и количество вызовов ReleaseMutex должно быть соответствующим.
Sleep ( dwMilliseconds ) ;
Процедура Sleep блокирует текущий поток на заданное количество миллисекунд. Если в качестве параметра передать INFINITE, поток будет заблокирован навсегда. Если передать ноль, поток уступает оставшуюся часть своей доли процессорного времени любой другой нити с таким же приоритетом. У процедуры нет возвращаемого значения (VOID).
ExitThread ( dwCode ) ;
ExitThread завершает текущий поток с заданным кодом возврата. Объект потока при этом переходит в сигнальное состояние. Если это был последний поток в текущем процессе, процесс завершается. Код возврата конкретного потока может быть получен с помощью GetExitCodeThread.
А теперь посмотрим на все это хозяйство в действии:
#define THREADS_NUMBER 10
#define ITERATIONS_NUMBER 100
#define PAUSE 10 /* ms */
DWORD dwCounter = 0 ;
DWORD WINAPI ThreadProc ( CONST LPVOID lpParam ) {
CONST HANDLE hMutex = ( CONST HANDLE ) lpParam ;
DWORD i ;
for ( i = 0 ; i < ITERATIONS_NUMBER ; i ++ ) {
WaitForSingleObject ( hMutex , INFINITE ) ;
dwCounter ++;
ReleaseMutex ( hMutex ) ;
Sleep ( PAUSE ) ;
}
ExitThread ( 0 ) ;
}
VOID Error ( CONST HANDLE hStdOut , CONST LPCWSTR szMessage ) {
DWORD dwTemp ;
TCHAR szError [ 256 ] ;
WriteConsole ( hStdOut , szMessage , lstrlen ( szMessage ) ,
wsprintf ( szError , TEXT ( «LastError = %d r n » ) , GetLastError ( ) ) ;
WriteConsole ( hStdOut , szError , lstrlen ( szError ) ,
ExitProcess ( 0 ) ;
}
INT main ( ) {
TCHAR szMessage [ 256 ] ;
DWORD dwTemp , i ;
HANDLE hThreads [ THREADS_NUMBER ] ;
CONST HANDLE hStdOut = GetStdHandle ( STD_OUTPUT_HANDLE ) ;
CONST HANDLE hMutex = CreateMutex ( NULL , FALSE , NULL ) ;
if ( NULL == hMutex ) {
Error ( hStdOut , TEXT ( «Failed to create mutex. r n » ) ) ;
}
for ( i = 0 ; i < THREADS_NUMBER ; i ++ ) {
hThreads [ i ] = CreateThread ( NULL , 0 ,
if ( NULL == hThreads [ i ] ) {
Error ( hStdOut , TEXT ( «Failed to create thread. r n » ) ) ;
}
}
WaitForMultipleObjects ( THREADS_NUMBER , hThreads , TRUE , INFINITE ) ;
wsprintf ( szMessage , TEXT ( «Counter = %d r n » ) , dwCounter ) ;
WriteConsole ( hStdOut , szMessage , lstrlen ( szMessage ) ,
for ( i = 0 ; i < THREADS_NUMBER ; i ++ ) {
CloseHandle ( hThreads [ i ] ) ;
}
CloseHandle ( hMutex ) ;
ExitProcess ( 0 ) ;
}
Здесь мы создаем десять потоков, каждый из которых увеличивает глобальный счетчик на единицу сто раз. Чтобы в счетчике не оказалась записана фигня, доступ к нему ограничивается с помощью мьютекса. Перед вызовом ExitProcess закрываются хэндлы мьютекса и всех созданных потоков. Строго говоря, в данном конкретном случае это не требуется, но в более общем случае все хэндлы за собой, разумеется, нужно вовремя закрывать. Также вы могли обратить внимание, что я прислушался к совету, который мне дали в комментариях к предыдущей заметке, и стал оборачивать строки в макрос TEXT.
Как обычно, программа может быть скомпилирована как в Visual Studio и запущена под Windows, так и с помощью MinGW и запущена под Wine. Ну и по традиции напоминаю, что ничто не делает блогера более грустным котиком, чем отсутствие каких-либо комментариев к его постам 🙂
Дополнение: Кстати, при помощи именованных мьютексов можно сделать так, чтобы пользователь мог запустить только один экземпляр приложения. Здесь можно посмотреть пример как это делается.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.
Источник: eax.me
Posix win threads for windows что это за программа
Стандарт POSIX допускает различные подходы к реализации многопоточности в рамках одного процесса. Возможны три основных подхода:
1. В пользовательском адресном пространстве, когда нити в пределах процесса переключаются собственным планировщиком.
2. Реализация при помощи системных нитей, когда переключение между нитями осуществляется ядром, так же, как и переключение между процессами.
3. Гибридная реализация, когда процессу выделяют некоторое количество системных нитей, но процесс имеет собственный планировщик в пользовательском адресном пространстве.
Как правило, при этом количество пользовательских нитей в процессе может превосходить количество системных нитей. [9]
Реализация планировщика в пользовательском адресном пространстве не представляет больших сложностей; наброски реализаций таких планировщиков приводятся во многих учебниках по операционным системам. Главным достоинством пользовательского планировщика считается тот факт, что он может быть реализован без изменений ядра системы.
При практическом применении такого планировщика, однако, возникает серьезная проблема. Если какая-то из нитей процесса исполняет блокирующийся системный вызов, блокируется весь процесс. Устранение этой проблемы требует серьезных изменений в механизме взаимодействия диспетчера системных вызовов с планировщиком операционной системы. То есть главное достоинство пользовательского планировщика при этом будет утеряно. Другим недостатком пользовательских нитей является то, что они не могут воспользоваться несколькими процессорами на многопроцессорной машине, потому что процесс всегда планируется только на одном процессоре.
Наиболее известная реализация пользовательских нитей — это волокна (fibers) в Win32. Волокна могут использоваться совместно с системными нитями Win32, но при этом волокна привязаны к определенной нити и исполняются только в контексте этой нити. Волокна не должны исполнять блокирующиеся системные вызовы; попытка сделать это приведет к блокировке нити. Некоторые системные вызовы Win32 имеют неблокирующиеся аналоги, предназначенные для использования в волокнах, но далеко не все. Это резко ограничивает применение волокон в реальных приложениях.
Ядро типичной современной ОС уже имеет планировщик, способный переключать процессы. Переделка этого планировщика для того, чтобы он мог переключать несколько нитей в пределах одного процесса, также не представляет больших сложностей. При этом возможны два подхода к такой переделке.
В рамках первого подхода, системные нити выступают как подчиненная по отношению к процессу сущность. Идентификатор нити состоит из идентификатора родительского процесса и собственного идентификатора нити. Идентификатор нити локален по отношению к процессу, т.е. нити разных процессов могут иметь одинаковые идентификаторы. Такой подход реализует большинство систем, реализующих системные нити — IBM MVS-OS/390-zOS, DEC VAX/VMS — OpenVMS, OS/2, Win32, многие Unix-системы, в том числе и Solaris. В Solaris и других Unix системах (IBM AIX, HP/UX) системные нити называются LWP (Light-Weight Process, «легкие процессы»).
Solaris 10 использует системные нити, так что каждой нити POSIX Threads API соответствует собственный LWP. Старые версии Solaris использовали гибридный подход, который рассматривается в следующем разделе.
В рамках другого подхода, системные нити являются сущностями того же уровня, что процесс. Иногда все, что объединяет нити одного процесса — это общее адресное пространство. Наиболее известная ОС, использующая такой подход — Linux. В Linux, нити выглядят как отдельные записи в таблице процессов и отдельные строки в выводе команд top (1) и ps (1), имеют собственный идентификатор процесса.
В старых версиях Linux это приводило к своеобразным проблемам при реализации POSIX Threads API; так, в большинстве Unix-систем завершение процесса системным вызовом exit (2) приводит к немедленному завершению всех его нитей; в Linux вплоть до 2.4 завершалась только текущая нить. В Linux 2.6 был внесен ряд изменений в ядро, приблизивших семантику многопоточности к стандарту POSIX. Эти изменения известны как NPT (Native POSIX Threads).
Разрабатываемый лабораторный практикум рассчитан на стандартную семантику POSIX Threads API. При программировании для старых (2.4 и младше) версий ядра Linux необходимо изучить особенности поведения этих систем по документации, поставляющейся с системой, или по другим источникам.
В гибридной реализации многопоточный процесс имеет несколько LWP и планировщик в пользовательском адресном пространстве. Этот планировщик переключает пользовательские нити между свободными LWP, подобно тому, как системныйпланировщик в многопроцессорной системе переключает процессы и системные нити между свободными процессами. При этом, как правило, процесс имеет больше пользовательских нитей, чем у него есть LWP.
Причина, по которой этот подход нашел практическое применение — это убеждение разработчиков первых многопоточных версий Unix, что пользовательские нити дешевле системных, требуют меньше ресурсов для своего исполнения.
При планировании пользовательских нитей возникает проблема блокирующихся системных вызовов. Когда какая-то нить вызывает блокирующийся системный вызов, соответствующий LWP блокируется и на некоторое время выпадает из работы.
В старых версиях Solaris эта проблема решалась следующим образом: многопоточная библиотека всегда имела выделенную нить, которая не вызывала блокирующихся системных вызовов никогда. Когда ядро системы обнаруживало, что все LWP процесса заблокированы, оно посылало процессу сигнал SIGWAITING.
Библиотечная нить перехватывала этот сигнал и, если это допускалось настройками библиотеки, создавала новый LWP. Таким образом, если все пользовательские нити исполняли блокирующиеся системные вызовы, то количество LWP могло сравняться с количеством пользовательских нитей.
Можно предположить, что компания Sun отказалась от гибридной реализации многопоточности именно потому, что обнаружилось, что такое происходит со многими реальными прикладными программами. В старых версиях Solaris поддерживался довольно сложный API, позволявший управлять количеством LWP и политикой планирования нитей между ними. Так, можно было привязать нить к определенному LWP.
Этот API был частью Solaris NativeThreads и нестандартным расширением POSIX Threads API. В рамках данного курса этот API не изучается. Многие современные Unix-системы, в том числе IBM AIX, HP/UX, SCO UnixWare используют гибридную реализацию POSIX Thread API.
Создание и завершение нитей
В POSIX Thread API нить создается библиотечной функцией pthread_create ().
Параметры этой функции:
· pthread_t * thread — Выходной параметр. Указатель на переменную, в которой при успешном завершении будет размещен идентификатор нити.
· const pthread_attr_t * attr — Входной параметр. Указатель на структуру, в которой заданы атрибуты нити (рассматривается на следующей лекции). Если этот указатель равен
· NULL, используются атрибуты по умолчанию.
· void * (*start_routine) (void*) — Входной параметр. Указатель на функцию, которая будет запущена во вновь созданной нити.
· void *arg — Входной параметр. Значение, которое будет передано в качестве параметра start_routine.
· 0 при успешном завершении
· Код ошибки при неудачном завершении
Большинство других функций POSIX Threads API используют аналогичное соглашение о кодах возврата.
Для завершения нити используется функция pthread_exit (). Эта функция всегда завершается успешно и принимает единственный параметр, указатель на код возврата типа void *. Как и в случае с pthread_create (), библиотека никогда не пытается обращаться к значению этого параметра как к указателю, поэтому его можно использовать для передачи скалярных значений.
Другой способ завершения нити — это возврат управления из функции start_routine при помощи оператора return. Поскольку функция start_routine должна возвращать тип void *, все ее операторы return должны возвращать значение. Этот способ практически полностью эквивалентен вызову pthread_exit () с тем же самым значением.
Для ожидания завершения нити и получения ее кода возврата используется библиотечная функция pthread_join (). Эта функция имеет два параметра, идентификатор нити типа pthread_t, и указатель на переменную типа void *, в которой размещается значение кода возврата. Если в качестве второго параметра передать нулевой указатель, код возврата игнорируется.
Если требуемая нить еще не завершилась, то нить, сделавшая вызов pthread_join (), блокируется. Если такой нити (уже) не существует, pthread_join () возвращает ошибку ESRCH.
Когда нить завершается, то связанные с ней ресурсы существуют до того момента, пока какаято другая нить не вызоветpthread_join (). Однако к тому моменту, когда pthread_join завершается, все ресурсы, занятые нитью (стек, thread local data, дескриптор нити) уничтожаются.
В отличие от процессов Unix, где системный вызов wait () может использовать только родитель по отношению к потомкам, любая нить может ждать завершения любой другой нити того же процесса. Если несколько нитей ждут завершения одной и той же нити, которая еще не завершилась, все эти нити блокируются. Однако при завершении нити одна из ожидавших нитей получает код возврата, а остальные ошибку ESRCH.
Если нить пытается ожидать сама себя, она получает ошибку EDEADLK.
Еще одна важная функция, связанная с ожиданием завершения нити — это функция pthread_detach (). Эта функция указывает, что все ресурсы, связанные с нитью, необходимо уничтожать сразу после завершения этой нити. При этом уничтожается и код возврата такой нити — при попытке сделать pthread_join () на нить, над которой перед этим сделали pthread_detach (), возвращается код ошибки EINVAL.
В руководстве по pthread_detach () в системе Solaris 10 сказано, что главное применение pthread_detach () — это ситуация, когда родитель, ожидавший завершения дочерней нити, получает pthread_cancel (). В действительности, существуют и другие применения «отсоединенных» нитей.
Не обязательно делать pthread_detach () на уже запущенную нить; в атрибутах нити (pthread_attr_t) можно указать, что нить следует запускать в уже «отсоединенном» состоянии.
Библиотечная функция pthread_cancel () принудительно завершает нить. В зависимости от свойств нити и некоторых других обстоятельств, нить может продолжать исполнение некоторое время после вызова pthread_cancel (3C).
Атрибуты нитей и управление нитями
При создании нити можно указать блок атрибутов нити при помощи второго параметра функции pthread_create (). Этот блок представляет собой структуру pthread_attr_t.
Стандарт POSIX требует рассматривать эту структуру как непрозрачный тип и использовать для изменения отдельных атрибутов функции просмотра и установки отдельных атрибутов. Для инициализации pthread_attr_t следует использовать функцию pthread_attr_init (). Эта функция имеет единственный параметр — pthread_attr_t *attr. Она устанавливает атрибуты в соответствии с табл.2.
Табл.2. Атрибуты нити.
Значение по умолчанию
Нить использует процессорное время, выделяемое процессу. В Solaris 9 и последующих версиях Solaris этот параметр не имеет практического значения
Нити создаются присоединенными (для освобождения ресурсов после завершения нити необходимо вызвать pthread_join (3C)).
Стек нити размещается системой
Стек нити имеет размер, определяемый системой
Нить имеет приоритет 0
Нить не наследует приоритет у родителя
Нить использует фиксированные приоритеты, задаваемые ОС. Используется вытесняющая многозадачность (нить исполняется, пока не будет вытеснена другой нитью или не заблокируется на примитиве синхронизации)
Кроме этих атрибутов, pthread_attr_t содержит некоторые другие атрибуты. Некоторые из этих атрибутов игнорируются Solaris 10, и введены для совместимости с другими реализациями POSIX Thread API.
Для изменения значений атрибутов необходимо использовать специальные функции. Для каждого атрибута определены две функции, pthread_attr_set%ATTRNAME% и pthread_attr_get%ATTRNAME%.
При создании нити используются те значения атрибутов, которые были заданы к моменту вызова pthread_create (). Изменения в pthread_attr_t, внесенные уже после запуска нити, игнорируются системой. Благодаря этому один и тот же блок атрибутов нити можно использовать для создания нескольких разных нитей с разными атрибутами.
Некоторые из атрибутов, например detachstate и priority, могут быть изменены у уже работающей нити. Для этого используются отдельные функции. Так, detachstate в атрибутах нити изменяется функцией pthread_attr_setdetachstate (), а у исполняющейся нити — функцией pthread_detach (). Pthread_detach () изменяет значение атрибута с JOINABLE на DETACHED; способа изменить этот атрибут с DETACHED на JOINABLE у работающей нити не существует.
Многие из атрибутов, например размер или местоположение стека, не могут быть изменены у работающей нити.
Перед уничтожением структуры pthread_attr_t необходимо вызвать функцию pthread_attr_destroy ().
Выводы
В данном разделе проведен анализ основных современных технологий параллельного программирования для разных типов архитектур ЭВМ и различных операционных систем. По результатам анализа определены те технологии параллельного программирования, которые будут представлены для изучения в рамках лабораторного практикума, а именно MPI, OpenMP и Posix Threads. Данные технологии охватывают все основные классы современных параллельных компьютеров, такие как массивно-параллельные системы, симметричные мультипроцессорные системы, системы с неоднородным доступом к памяти и кластерные системы, а также основные операционные системы (Windows и Unix-подобные системы). Обозначены основные директивы и функции вышеупомянутых технологий, которые могут быть предложены для освоения студентами.
Источник: studbooks.net
Posix win threads for windows что это за программа
Originally posted by Dian [b]Шаг вполне логичен со стороны Микрософта. Причем не совсем потому, что «нужно что-то своё» — строго говоря, абсолютно никакой надобности в своём API не было. |
Не совсем согласен. В WinAPI есть интересные вещи, которых нет в POSIX. И это действительно предоставляет дополнительные возможности.
Просто это часть монополистической политики M$. Как Win32API, так и .NET |
По поводу wmv: не совсем согласен с таким мнением. Дело в том, что контент, который обычно содержит wmv файл, есть подправленный (а может даже и чистый) MPEG 4 (для этого можно посмотреть библиотеку ffmpeg, где явно видно, что для декодирования информации из MOV, MPEG 4 Quicktime, wmv, AVI используются одни и те же функции). Другое дело — сам контейнер. Я это считаю здоровой конкуренцией с Apple.
Если даже сейчас не очень распространен контейнер, описываемый ISO/IEC, явно с подачи Apple, то раньше. Т.е. не факт, что по удобствам, которые предоставляет wmv для продажи контента, выиграет ISOшный контейнер.
Источник: www.hardforum.ru
Программирование C в Linux — потоки pthreads
Многопоточность в программировании является важным механизмом в наше время. Поэтому я решил посвятить несколько статей этой теме.
В семействах ОС Windows — каждая программа запускает один процесс выполнения, в котором находится как минимум один поток (нить). В процессе может находиться множество потоков, между которыми делится процессорное время. Один процесс не может напрямую обратиться к памяти другого процесса, а потоки же разделяют одно адресное пространство одного процесса. То есть в Windows — процесс это совокупность потоков.
В Linux же немного по-другому. Сущность процесса такая же, как и в Windows — это исполняемая программа со своими данными. Но вот поток в Linux является отдельным процессом (можно встретить название как «легковесный процесс», LWP). Различие такое же — процесс отдельная программа со своей памятью, не может напрямую обратиться к памяти другого процесса, а вот поток, хоть и отдельный процесс, имеет доступ к памяти процесса-родителя [2]. LWP процессы создаются с помощью системного вызова clone() с указанием определенных флагов.
Но также имеется такая вещь, которая называется «POSIX Threads» — библиотечка стандарта POSIX, которая организует потоки (они же нити) внутри процесса. Т.е тут уже распараллеливание происходит в рамках одного процесса.
И тут встает вопрос различия терминов «поток», «процесс», «нить» и т.д. Проблема в том, что в англоязычной литературе данные термины определяются однозначно, у нас же с нашим великим и могучим имеются противоречия, что может привести к дикому диссонансу.
Но это все в общих чертах, для более точной информации следует обратиться к соответствующей литературе, либо к официальной документации, можно почитать man’ы. В конце статьи я приведу несколько полезных ссылок на ресурсы, где более подробно расписано как все работает, а пока займемся практикой.
Я рассмотрю два варианта «распараллеливания» программы — создания потока/нити с помощью функций из pthread.h (POSIX Threads), либо создание отдельного процесса с помощью функции fork().
Сегодня рассмотрим потоки из библиотеки pthread.
Шаблон кода для работы с потоками выглядит следующим образом:
Источник: tetraquark.ru