#define NUM_THREADS 6
void *thread_function(void *arg);
int main() {
int res;
pthread_t a_thread[NUM_THREADS];
void *thread_result;
int lots_of_threads;
for (lots_of_threads = 0; lots_of_threads < NUM_THREADS; lots_of_threads++) {
<i> res = pthread_create(&(a_thread[lots_of_thread]), NULL,</i>
<i> thread_function, (void*)lots_оf_threads);</i>
<i> if (res != 0) {</i>
<i> perror("Thread creation failed");</i>
<i> exit(EXIT_FAILURE);</i>
<i> }</i>
}
printf("Waiting for threads to finish...n");
for (lots_of_threads = NUM_THREADS - 1; lots_of_threads >= 0;
lots of threads--) {
res = pthread_join(a_thread[lots_of_threads], &thread_result);
if (res == 0) {
printf("Picked up a threadn");
} else {
perror("pthread_join failed");
}
}
printf("All donen");
exit(EXIT_SUCCESS);
}
<i>void* thread_function(void* arg) {</i>
<i> int my_number = (int)arg;</i>
int rand_num;
printf("thread_function is running. Argument was %dn", my_number);
rand_num = 1+(int)(9.0*rand()/(RAND_MAX+1.0));
sleep(rand_num);
printf("Bye from %dn", my_number);
pthread_exit(NULL);
}
Резюме
В этой главе вы узнали, как создать несколько потоков исполнения внутри процесса, которые совместно используют глобальные переменные. Вы рассмотрели два способа управления — семафоры и мьютексы, применяемые потоками для доступа к важным фрагментам кода и данным. Далее вы увидели, как управлять атрибутами потоков и, в особенности, как можно отсоединить потоки от основного, не заставляя его ждать завершения созданных им потоков. После краткого обзора способов формирования в одном потоке запросов на отмену других потоков и вариантов управления такими запросами в потоке, получившем их, мы представили программу с множественными одновременно выполняющимися потоками.
Объем книги не позволяет обсудить все до единой функции и тонкости, связанные с потоками, но теперь у вас достаточно знаний для того, чтобы начать писать собственные программы, применяющие потоки, и изучать глубоко скрытые свойства потоков, читая страницы интерактивного справочного руководства.
Глава 13
Связь между процессами: каналы
В главе 11 вы видели очень простой способ пересылки сообщений между процессами с помощью сигналов. Вы формировали уведомляющие события, которые могли бы применяться для вызова ответа, но передаваемая информация была ограничена номером сигнала.
В этой главе вы познакомитесь с каналами, которые позволяют процессам обмениваться более полезной информацией. В конце этой главы вы примените свои вновь приобретенные знания для новой реализации программы, управляющей базой данных компакт-дисков, в виде клиент-серверного приложения.
В данной главе мы обсудим следующие темы:
□ определение канала;
□ каналы процессов;
□ вызовы каналов;
□ родительские и дочерние процессы;
□ именованные каналы — FIFO;
□ замечания, касающиеся клиент-серверных приложений.
Что такое канал?
Мы применяем термин "канал" для обозначения соединения потока данных одного процесса с другим. Обычно вы присоединяете или связываете каналом вывод одного процесса с вводом другого.
Большинство пользователей Linux уже знакомы с идеей конвейера, связывающего вместе команды оболочки так, что вывод одного процесса поставляет данные прямо во ввод другого. В случае команд оболочки это делается с помощью символа конвейера или канала, соединяющего команды следующим образом:
cmd1 | cmd2
Командная оболочка организует стандартный ввод и вывод двух команд так, что:
□ стандартный ввод cmd1 поступает с клавиатуры терминала;
□ стандартный вывод cmd1 поставляется cmd2 как ее стандартный ввод;
□ стандартный вывод cmd2 подсоединен к экрану терминала.
На самом деле командная оболочка заново соединила потоки стандартных ввода и вывода так, что потоки данных проходят с клавиатурного ввода через две команды и выводятся на экран. На рис. 13.1 приведено визуальное представление этого процесса.
Рис. 13.1
В этой главе вы увидите, как достичь этого эффекта в программе и как можно использовать каналы для связи многих процессов, что позволит создать простую клиент-серверную систему.
Каналы процессов
Возможно, простейший способ передачи данных между программами — применение функций popen и pclose. У них следующие прототипы:
<b>#include <stdio.h></b>
<b>FILE *popen(const char *command, const char *open_mode);</b>
<b>int pclose(FILE *stream_to_close);</b>
popen
Функция popen позволяет программе запустить другую программу как новый процесс и либо передать ей данные, либо получить их из нее. Строка command — это имя программы для выполнения вместе с любыми параметрами, параметр open_mode должен быть "r" или "w".
Если open_mode — "r", вывод вызванной программы становится доступен вызывающей программе и может быть считан из возвращаемого функцией popen файлового потока FILE* с помощью обычных функций библиотеки stdio, предназначенных для чтения (например, fread). Но если open_mode — "w", программа может отправить данные вызванной команде с помощью вызова функции fwrite. Далее вызванная программа сможет читать данные из своего стандартного ввода. Обычно вызванная программа не знает, что она считывает данные из другого процесса; она просто читает свой поток стандартного ввода и воздействует на него.