Многозадачность и синхронизация в Plan 9: Часть 2

В этой статье речь пойдет о библиотеке thread(2) которая расширяет возможности по организации многозадачных программ в Plan 9. Библиотека предоставляет набор функций для создания процессов и потоков внутри них. Процесс ни что иное, как системный процесс Plan 9, создается посредством rfork. Нить - процедура, которая выполняется в контексте своего процесса, но с отдельным стеком, нитей в процессе может быть несколько. Нить исполняется сколь угодно долго, в контексте своего процесса, пока явно не передаст управление другой нити. Так например вызов sleep(1000) в одной из нитей процесса, не означает что остальные нити получат управление - весь процесс погрузится в "сон" на 1 секунду.

Следует заметить, что использование QLock возможно благодаря тому, что libc которая предоставляет этот примитив, имеет поддержку для libthread. Файл /sys/src/libc/9sys/qlock.c содержит указатель на функцию _rendezvousp которая по умолчанию указывает на rendezvous, а при использовании библиотеки libthread переустанавливается на собственный вариант, так как взаимодействие может происходить как между нитями разных процессов (что поддерживает и стандартный rendezvous) так и между нитями в одном процессе.

Чтобы понять, как работает библиотека можно «поиграть» со следующим примером:
01	#include <u.h>
02	#include <libc.h>
03	#include <thread.h>
04	
05	void thread(void* data)
06	{
07		int a,i;
08	
09		a=(int)data;
10	
11		for(i=0;i<15;i++)
12		{
13			print("%d\n",a);
14		//	if (!(i % 2))
15		//		yield();
16		}
17	
18		threadexits(0);
19	}
20	
21	void threadmain(int ,char** )
22	{
23		int i;
24	
25		threadcreate(thread,(void*)1,1024);
26		threadcreate(thread,(void*)2,1024);
27		//proccreate(thread,(void*)3,1024);
28	
29		//yield();
30	
31		for (i=0;i<10;i++)
32			print("main\n");
33		
34		threadexits(0);
35		//threadexitsall(0);
36	}
Исполнение программы начинается со строки 21, процедура threadmain, которая вызывается после инициализации библиотеки libthread. В строках 25 и 26 создаются две нити, для них выделяется стек (посредством malloc), заполняются необходимые структуры, они помечаются как готовые к выполнению и ставятся в очередь (FIFO). Далее выполнение продолжается со строки 31, экран заполняется 10 строчками «main», и посредством threadexit, строка 34 текущая нить завершается, далее из очереди изымается следующая «готовая к исполнению» нить и управление передается ей, экран заполняется 10 строчками «1», после чего нить так же завершается. Таким же образом управление передается последней нити, и печатаются строки «2». Когда и эта нить завершается, и в очереди больше нет нитей, то полностью завершается весь процесс. Данное поведение похоже на последовательный вызов:
main;
thread(1);
thread(2);
В этом примере так и есть, но в любой момент любая нить может передать управление следующей нити, обратившись к yield, proccreate, procexec, procexecl, threadexits, alt, send, recv. Что бы посмотреть, как работает yield можно поэкспериментировать со строчками 29 или 14-15. Yield перемещает нить в конец очереди, оставляя ее «готовой к исполнению». Так если «освободить» строчку 29 то наша нить «main» после создания двух нитей поставит себя в конец очереди, и на экране мы увидим такую очередность: 1-2-main. А если создать дополнительно еще один процесс, строчка 27, то мы увидим, как он исполняется параллельно. Для знакомства с Каналами предлагаю вам такой пример:
01	#include <u.h>
02	#include <libc.h>
03	#include <thread.h>
04	
05	int mainstacksize=1024;
06	
07	Channel *t;
08	
09	void timerproc(void* idx)
10	{
11		ulong index=(ulong)idx;
12	
13		for(;;)
14		{
15			sleep(index);
16			sendul(t,index);
17		}
18	}
19	
20	void threadmain(int, char**)
21	{
22		ulong idx,i;
23		t=chancreate(sizeof(ulong),0);
24	
25		proccreate(timerproc,(void*)1000,1024);
26		proccreate(timerproc,(void*)500,1024);
27	
28		for(i=0;i<10;i++)
29		{
30			idx=recvul(t);
31			print("Timer %uld\n",idx);
32		}
33	
34		threadexitsall(0);
35	}
Программа состоит из трех процессов, один основной, и два процесса-таймера. Основной процесс создает канал t, с размером элемента (шириной, если можно так сказать) sizeof(ulong) – 4 байта. Посредством proccreate порождаются два процесса, которые с заданным интервалом передают в канал сообщение. Основной поток 10 раз принимает сообщение, при этом, печатая принятое значение, после чего завершается. Следует заметить, что threadexitsall завершает полностью все потоки, а соответственно и процессы которые созданы библиотекой libthread, поэтому «вечный цикл» в timerproc не является проблемой.
Продолжение следует...
Хостинг от uCoz