Русские Блоги
Зачем нам нужно вводить потоки после того, как у нас есть концепция процессов? Каковы преимущества использования многопоточности? В какой системе следует использовать многопоточность? Сначала мы должны ответить на эти вопросы.
- Одной из причин использования многопоточности является то, что это очень «экономная» многозадачная операция по сравнению с процессами. . Мы знаем, что в системе Linux запуск нового процесса должен быть назначен его независимому адресному пространству и создано большое количество таблиц данных для поддержки сегмента кода, сегмента стека и сегмента данных. Это своего рода «дорогостоящая» многозадачность. Способ работы. Однако несколько потоков, запущенных в процессе, используют одно и то же адресное пространство друг с другом и совместно используют большую часть данных. Пространство, затрачиваемое на запуск потока, намного меньше места, затрачиваемого на запуск процесса, и потоки переключаются между собой. Требуемое время намного меньше, чем время, необходимое для переключения между процессами. Согласно статистике, в целом стоимость процесса примерно в 30 раз превышает стоимость потока.Конечно, эти данные могут сильно отличаться в конкретной системе.
- Вторая причина использования многопоточности — удобный механизм связи между потоками. . Для разных процессов у них есть независимые пространства данных, и передача данных может осуществляться только через коммуникацию.Этот метод не только трудоемкий, но и очень неудобен. Это не относится к потокам. Поскольку пространство данных совместно используется потоками в одном процессе, данные одного потока могут напрямую использоваться другими потоками, что не только быстро, но и удобно. Конечно, совместное использование данных порождает и другие проблемы. Некоторые переменные не могут быть изменены двумя потоками одновременно. Данные, объявленные как статические в некоторых подпрограммах, с большей вероятностью могут нанести катастрофический ущерб многопоточным программам. Это самый важный момент при написании многопоточных программ.
Помимо упомянутых выше преимуществ, многопоточные программы как метод многозадачности и одновременной работы, безусловно, имеют следующие преимущества:
- Улучшение отклика приложения. Это особенно важно для программ с графическим интерфейсом. Когда операция занимает много времени, вся система будет ждать ее. В это время программа не будет реагировать на операции с клавиатурой, мышью и меню. Использование технологии многопоточности займет много времени. Операция (занимающая много времени) помещается в новый поток, чтобы избежать этой неприятной ситуации.
- Сделайте многопроцессорную систему более эффективной. Операционная система гарантирует, что когда количество потоков не превышает количество процессоров, разные потоки будут выполняться на разных процессорах.
- Улучшить структуру программы. Длинный и сложный процесс можно разделить на несколько потоков и превратить в несколько независимых или полунезависимых выполняющихся частей.Такая программа облегчит понимание и модификацию.
Один, идентификация потока
- У потока есть идентификатор, но он не единственный в системе, а единственный действительный в среде процесса;
- Дескриптор потока имеет тип pthread_t, Этот тип нельзя рассматривать как целое число, а как структуру 。
- Заголовочный файл:
- опытный образец:int pthread_equal(pthread_t tid1, pthread_t tid2);
- Возвращаемое значение: равенство возвращает ненулевое значение, неравенство возвращает ноль.
- Описание :: Сравните, равны ли два идентификатора потока.
- Заголовочный файл:
- Прототип: pthread_t pthread_self ();
- Возвращаемое значение: возвращает идентификатор вызывающего потока.
Два, создание потока
Создайте поток во время выполнения, вы можете выделить работу, которую он должен выполнить для потока (функция выполнения потока), поток разделяет ресурсы процесса. Функция для создания потока: pthread_create ()
#include int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
- Возвращаемое значение: в случае успеха возвращает 0, в противном случае возвращает номер ошибки.
- параметр:
- tidp: переменная, указывающая на вновь созданный идентификатор потока в качестве вывода функции;
- attr: используется для настройки различных атрибутов потока, NULL является атрибутом по умолчанию (см. ниже);
- Недавно созданная ветка от start_rtn Адрес функции начинает выполняться, функция имеет только один void Параметры указателя типа arg ,в случае start_rtn Если требуется несколько параметров, вы можете поместить параметры в структуру, а затем использовать адрес структуры как arg Входящий. В Функция может возвращать возвращаемое значение типа void *, и это возвращаемое значение также может быть другого типа и получается с помощью pthread_join ();
- arg: единственный параметр void-указателя функции. Если вы хотите передать несколько параметров, вы можете использовать инкапсуляцию структуры.
Метод компиляции многопоточной программы под linux:
Поскольку библиотека pthread не является библиотекой системы Linux, ее необходимо добавить при компиляции -lpthread
# gcc filename -lpthread // По умолчанию gcc использует библиотеку C. Если вы хотите использовать дополнительные библиотеки, вы должны выбрать библиотеку для использования.
Пример: Давайте посмотрим на следующий пример. В этом примере программа создает поток и печатает идентификатор процесса, идентификатор нового потока и идентификатор потока начального потока.
#include "apue.h" #include pthread_t ntid; void printids(const char *s) < pid_t pid; pthread_t tid; pid = getpid(); tid = pthread_self(); printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid,(unsigned long)tid, (unsigned long)tid); >void *thr_fn(void *arg) < printids("new thread: "); return((void *)0); >int main(void)
Скомпилируйте и запустите:
Как мы и ожидали, идентификатор процесса тот же 10310, а идентификатор потока другой. Если основной поток не спит, он может выйти до выполнения нового потока. Следующее является результатом повторного выполнения после удаления. Очевидно, что новый поток не имеет возможности запуститься во время первого выполнения:
Три, завершение потока
Если какой-либо поток процесса вызывает функцию exit, _exit или _Exit, то весь процесс завершается. Обратите внимание, что здесь завершается весь процесс. Одиночный поток может выйти тремя способами, здесь это только выход из потока без завершения всего процесса.
- Поток может просто вернуться из процесса запуска, а возвращаемое значение — это код выхода потока.
- Поток может быть отменен другими потоками в том же процессе.
- Поток вызывает pthread_exit.
- Функция pthread_exit:
- Прототип: void pthread_exit (void * rval_ptr);
- Заголовочный файл:
- Параметры:rval_ptr — это нетипизированный указатель, который указывает на переменную хранения возвращаемого значения потока.
- Функция pthread_join:
- Прототип: int pthread_join (поток pthread_t, void ** rval_ptr);
- Заголовочный файл:
- Возвращаемое значение: в случае успеха возвращает 0, в противном случае возвращает номер ошибки.
- параметр:
- thread: идентификатор потока.
- rval_ptr: Указатель на возвращаемое значение (возвращаемое значение также является указателем) .
- Описание:
- Вызывающий поток будет блокироваться до тех пор, пока указанный поток не вызовет pthread_exit, не вернется из процедуры запуска или не будет отменен.
- Если поток возвращается из своей процедуры запуска, rval_ptr содержит код возврата .
- Если поток отменяется, блок памяти, указанный в rval_ptr, устанавливается в: PTHREAD_CANCELED.
- Если вас не волнует возвращаемое значение, вы можете установить rval_ptr в NULL.
Простая проверка приведенного выше содержимого на примере исходного кода выглядит следующим образом:
#include "apue.h" #include void *thr_fn1(void *arg) < printf("thread 1 returning\n"); return((void *)1); >void *thr_fn2(void *arg) < printf("thread 2 exiting\n"); pthread_exit ((void *) 2); // вызов pthread_exit () >int main(void) < int err; pthread_t tid1, tid2; void *tret; err = pthread_create(&tid1, NULL, thr_fn1, NULL); if (err != 0) err_exit(err, "can't create thread 1"); err = pthread_create(&tid2, NULL, thr_fn2, NULL); if (err != 0) err_exit(err, "can't create thread 2"); err = pthread_join (tid1, & tret); // Получить статус выхода потока 1 if (err != 0) err_exit(err, "can't join with thread 1"); printf("thread 1 exit code %ld\n", (long)tret); err = pthread_join (tid2, & tret); // Получить статус выхода из потока 2 if (err != 0) err_exit(err, "can't join with thread 2"); printf("thread 2 exit code %ld\n", (long)tret); exit(0); >
Результаты бега следующие (немного отличаются от результатов, приведенных в книге), и выводы можно сделать из следующих результатов бега.
Я обнаружил что-то странное в процессе набора кода void *ret ; Непосредственно применять указатель void для хранения данных, хотя указатель на память, на которую указывает указатель void, отсутствует, но сам указатель void имеет адрес , Второй параметр pthread_join имеет тип «void **», поэтому назначьте адрес tret второму параметру, и можно будет найти окончательный результат выполнения программы. Код возврата помещается прямо в адресное пространство, где находится tret . Есть небольшая проблема, о которой нужно знать, Память, используемая параметрами указателя void, используемыми в функциях pthread_create и pthread_exit, должна оставаться действительной после того, как вызывающий объект завершит вызов. , Итак, чтобы решить эту проблему, вы можете использовать глобальную структуру или использовать функцию malloc для выделения структуры. В книге также приведен пример, эксперимент:
#include "apue.h" #include struct foo < int a, b, c, d; >; void printfoo(const char *s, const struct foo *fp) < printf("%s", s); printf(" structure at 0x%lx\n", (unsigned long)fp); printf(" foo.a = %d\n", fp->a); printf(" foo.b = %d\n", fp->b); printf(" foo.c = %d\n", fp->c); printf(" foo.d = %d\n", fp->d); > void *thr_fn1(void *arg) < struct foo foo = ; printfoo("thread 1:\n", &foo); pthread_exit((void *)&foo); > void *thr_fn2(void *arg) < printf("thread 2: ID is %lu\n", (unsigned long)pthread_self()); pthread_exit((void *)0); >int main(void) < int err; pthread_t tid1, tid2; struct foo *fp; // поток 1 err = pthread_create(&tid1, NULL, thr_fn1, NULL); if (err != 0) err_exit(err, "can't create thread 1"); err = pthread_join(tid1, (void *)&fp); if (err != 0) err_exit(err, "can't join with thread 1"); sleep(1); // поток 2 printf("parent starting second thread\n"); err = pthread_create(&tid2, NULL, thr_fn2, NULL); if (err != 0) err_exit(err, "can't create thread 2"); sleep(1); printfoo("parent:\n", fp); exit(0); >
Память, используемая параметрами указателя void, используемыми в функциях pthread_create () и pthread_exit * (), должна оставаться действительной после того, как вызывающий объект завершит вызов.
Поток может вызватьpthread_cancelФункция для запроса отмены других потоков в том же процессе. Прототип функции выглядит следующим образом:
#include int pthread_cancel(pthread_t thread);
- Обратите внимание, что pthread_cancel не ждет завершения потока, он только делает запрос, и отмененный поток может выбрать, как ответить на этот запрос.
- Поток может упорядочивать функции, которые необходимо вызывать при выходе, что аналогично процессу, который может использовать функцию atexit для упорядочивания функций, которые необходимо вызывать при выходе из процесса. Поток может создать несколько обработчиков очистки. Обработчики записываются в стек, что означает, что их порядок выполнения противоположен порядку, в котором они были зарегистрированы.
#include void pthread_cleanup_push(void(*rtn)(void*),void *arg); void pthread_cleanup_pop(int execute);
Функция очистки вызывается, когда поток выполняет следующие действия, параметр вызова — arg, а последовательность вызова функции очистки организована с помощью pthread_cleanup_push.
- Когда вызывается pthread_exit
- При ответе на запрос об отмене
- При вызове pthread_cleanup_pop с ненулевым параметром выполнения.
Пример приведен в книге, давайте также поэкспериментируем:
#include "apue.h" #include void cleanup(void *arg) < printf("cleanup: %s\n", (char *)arg); >void *thr_fn1(void *arg) < printf("thread 1 start\n"); pthread_cleanup_push(cleanup, "thread 1 first handler"); pthread_cleanup_push(cleanup, "thread 1 second handler"); printf("thread 1 push complete\n"); if (arg) return ((void *) 1); // Если поток завершается возвратом из подпрограммы запуска, его обработчик очистки не вызывается. pthread_cleanup_pop(0); pthread_cleanup_pop(0); return((void *)1); >void *thr_fn2(void *arg) < printf("thread 2 start\n"); pthread_cleanup_push(cleanup, "thread 2 first handler"); pthread_cleanup_push(cleanup, "thread 2 second handler"); printf("thread 2 push complete\n"); if (arg) pthread_exit((void *)2); pthread_cleanup_pop(0); pthread_cleanup_pop (0); // функции pthread_cleanup_push, pthread_cleanup_pop должны появляться парами; pthread_exit((void *)2); >int main(void) < int err; pthread_t tid1, tid2; void *tret; err = pthread_create(&tid1, NULL, thr_fn1, (void *)1); if (err != 0) err_exit(err, "can't create thread 1"); err = pthread_create(&tid2, NULL, thr_fn2, (void *)1); // err = pthread_create (& tid2, NULL, thr_fn2, NULL); // Из-за использования NULL зарегистрированная функция очистки удаляется напрямую, и нет функции очистки для вызова pthread_exit. if (err != 0) err_exit(err, "can't create thread 2"); err = pthread_join(tid1, &tret); if (err != 0) err_exit(err, "can't join with thread 1"); printf("thread 1 exit code %ld\n", (long)tret); err = pthread_join(tid2, &tret); if (err != 0) err_exit(err, "can't join with thread 2"); printf("thread 2 exit code %ld\n", (long)tret); exit(0); >
- Поскольку указанная выше функция вызывается с 0 параметрами, зарегистрированная функция очистки удаляется напрямую, и при вызове pthread_exit функция очистки не вызывается.
- Функции pthread_cleanup_push и pthread_cleanup_pop должны появляться парами.
- Если поток завершается возвратом из процедуры запуска, его обработчик очистки не вызывается.
Поток можно отсоединить, вызвав функцию pthread_detach:
#include int pthread_detach(pthread_t thread);
- Если поток был отсоединен, ресурсы хранения, лежащие в основе потока, могут быть восстановлены немедленно, когда поток завершается.
- После отсоединения потока вы не можете использовать pthread_join для ожидания его статуса завершения.