45 Sem_wait(shared.mutex);
46 shared.counter++;
47 Sem_post(shared.mutex);
48 }
49 return(NULL);
50 }
Листинг А.26. Функция main для измерения быстродействия именованных семафоров Posix
//bench/incr_pxsem2.с
1 #include "unpipc.h"
2 #define MAXNTHREADS 100
3 #define NAME "incr_pxsem2"
4 int nloop;
5 struct {
6 sem_t *mutex; /* указатель на именованный семафор */
7 long counter;
8 } shared;
9 void *incr(void *);
10 int
11 main(int argc, char **argv)
12 {
13 int i, nthreads;
14 pthread_t tid[MAXNTHREADS];
15 if (argc != 3)
16 err_quit("usage: incr_pxsem2 <#loops> <#threads>");
17 nloop = atoi(argv[1]);
18 nthreads = min(atoi(argv[2]), MAXNTHREADS);
19 /* инициализация именованного семафора 0 */
20 sem_unlink(Px_ipc_name(NAME)); /* ошибка – OK */
21 shared.mutex = Sem_open(Px_ipc_name(NAME), O_CREAT | O_EXCL, FILE_MODE, 0);
22 /* создание всех потоков */
23 Set_concurrency(nthreads);
24 for (i = 0; i < nthreads; i++) {
25 Pthread_create(&tid[i], NULL, incr, NULL);
26 }
27 /* запуск таймера и разблокирование семафора */
28 Start_time();
29 Sem_post(shared.mutex);
30 /* ожидание завершения всех потоков */
31 for (i = 0; i < nthreads; i++) {
32 Pthread_join(tid[i], NULL);
33 }
34 printf("microseconds: %.0f usecn", Stop_time());
35 if (shared.counter != nloop * nthreads)
36 printf("error: counter = %ldn", shared.counter);
37 Sem_unlink(Px_ipc_name(NAME));
38 exit(0);
39 }
Функция main программы, измеряющей быстродействие семафоров System V, приведена в листинге А.27, а функция incr показана в листинге А.28.
Листинг А.27. Функция main для измерения быстродействия семафоров System V
//bench/incr_svsem1.c
1 #include "unpipc.h"
2 #define MAXNTHREADS 100
3 int nloop;
4 struct {
5 int semid;
6 long counter;
7 } shared;
8 struct sembuf postop, waitop;
9 void *incr(void *);
10 int
11 main(int argc, char **argv)
12 {
13 int i, nthreads;
14 pthread_t tid[MAXNTHREADS];
15 union semun arg;
16 if (argc != 3)
17 err_quit("usage: incr_svseml <#loops> <#threads>");
18 nloop = atoi(argv[1]);
19 nthreads = min(atoi(argv[2]), MAXNTHREADS);
20 /* создание семафора и инициализация его значением 0 */
21 shared.semid = Semget(IPC_PRIVATE, 1, IPC_CREAT | SVSEM_MODE);
22 arg.val =0;
23 Semctl(shared.semid, 0, SETVAL, arg);
24 postop.sem_num = 0; /* инициализация двух структур semop */
25 postop.sem_op = 1;
26 postop.sem_flg = 0;
27 waitop.sem_num = 0;
28 waitop.sem_op = –1;
29 waitop.sem_flg = 0;
30 /* создание всех потоков */
31 Set_concurrency(nthreads);
32 for (i = 0; i < nthreads; i++) {
33 Pthread_create(&tid[i], NULL, incr, NULL);
34 }
35 /* запуск таймера и разблокирование семафора */
36 Start_time();
37 Semop(shared.semid, &postop, 1); /* up by 1 */
38 /* ожидание завершения всех потоков */
39 for (i = 0; i < nthreads; i++) {
40 Pthread_join(tid[i], NULL);
41 }
42 printf("microseconds: %.0f usecn", Stop_time());
43 if (shared.counter != nloop * nthreads)
44 printf("error: counter = %ldn", shared, counter);
45 Semctl(shared.semid, 0, IPC_RMID);
46 exit(0);
47 }
Листинг А.28. Увеличение общего счетчика с использованием семафоров System V
//bench/incr_svsem1.c
48 void *
49 incr(void *arg)
50 {
51 int i;
52 for (i = 0; i < nloop; i++) {
53 Semop(shared.semid, &waitop, 1);
54 shared.counter++;
55 Semop(shared.semid, &postop, 1);
56 }
57 return(NULL);
58 }
20-23 Создается семафор с одним элементом, значение которого инициализируется нулем.
24-29 Инициализируются две структуры semop: одна для увеличения семафора, а другая для ожидания его изменения. Обратите внимание, что поле sem_flg в обеих структурах имеет значение 0: флаг SEM_UNDO не установлен.
Семафоры System V с флагом SEM_UNDO
Единственное отличие от пpoгрaммы из листинга А.27 заключается в том, что поле sem_flg структур semop устанавливается равным SEM_UNDO, а не 0. Мы не приводим в книге новый листинг с этим небольшим изменением.
Последняя пpoгрaммa использует fcntl для синхронизации. Функция main приведена в листинге А.30. Эта программа будет выполняться успешно только в том случае, если количество потоков равно 1, поскольку блокировка fcntl предназначена для использования между процессами, а не между потоками одного процесса. При указании нескольких потоков каждый из них всегда имеет возможность получить блокировку (то есть вызовы writew_lock не приводят к остановке потока, потому что процесс уже является владельцем блокировки), и конечное значение счетчика оказывается неправильным.
Функция incr, использующая блокировку записей, приведена в листинге А.29.
Листинг А.29. Увеличение общего счетчика с использованием блокировки записей fcntl
//bench/incr_fcntl1.e
44 void *
45 incr(void *arg)
46 {
47 int i;
48 for (i = 0; i < nloop; i++) {
49 Writew_lock(shared.fd, 0, SEEK_SET, 0);
50 shared.counter++;
51 Un_lock(shared.fd, 0, SEEK_SET, 0);
52 }
53 return(NULL);
54 }
Листинг А.30. Функция main для измерения производительности блокировки fcntl
//bench/incr_fcntl1.e
4 #include "unpipc.h"
5 #define MAXNTHREADS 100
6 int nloop;
7 struct {
8 int fd;
9 long counter;
10 } shared;
11 void *incr(void *);
12 int
13 main(int argc, char **argv)
14 {
15 int i, nthreads;
16 char *pathname;
17 pthread_t tid[MAXNTHREADS];
18 if (argc != 4)
19 err_quit("usage: incr_fcntll <pathname> <#loops> <#threads>");
20 pathname = argv[1];
21 nloop = atoi(argv[2]);
22 nthreads = min(atoi(argv[3]), MAXNTHREADS);
23 /* создание файла и получение блокировки на запись */
24 shared.fd = Open(pathname, O_RDWR | O_CREAT | O_TRUNC, FILE_MODE);
25 Writew_lock(shared.fd, 0, SEEK_SET, 0);
26 /* создание всех потоков */
27 Set_concurrency(nthreads);
28 for (i = 0; i < nthreads; i++) {
29 Pthread_create(&tid[i], NULL, incr, NULL);
30 }
31 /* запуск таймера и снятие блокировки на запись */
32 Start_time();
33 Un_lock(shared.fd, 0, SEEK_SET, 0);
34 /* ожидание завершения всех потоков */
35 for (i = 0; i < nthreads; i++) {
36 Pthread_join(tid[i], NULL);
37 }
38 printf("microseconds: %.0f usecn", Stop_time());
39 if (shared.counter != nloop * nthreads)
40 printf("error: counter = %ldn", shared.counter);
41 Unlink(pathname);
42 exit(0);
43 }
15-19 Полное имя создаваемого и используемого для блокировки файла принимается в качестве аргумента командной строки. Это позволяет измерять скорость работы для разных файловых систем. Можно ожидать, что программа будет работать гораздо медленнее при использовании NFS (если она вообще будет работать, то есть если сервер и клиент NFS поддерживают блокировку записей NFS).
А.6. Синхронизация процессов: программы
В программах предыдущего раздела счетчик использовался несколькими потоками одного процесса. При этом он представлял собой глобальную переменную. Теперь нам нужно изменить эти программы для измерения скорости синхронизации различных процессов.
Для разделения счетчика между родительским процессом и дочерними мы помещаем его в разделяемую память, выделяемую функцией my_shm, показанной в листинге А.31.
Листинг А.31. Выделение разделяемой памяти под родительский и дочерние процессы
//lib/my_shm.c
1 #include "unpipc.h"
2 void *
3 my_shm(size_t nbytes)
4 {
5 void *shared;
6 #if defined(MAP_ANON)
7 shared = mmap(NULL, nbytes, PROT_READ | PROT_WRITE,
8 MAP_ANON | MAP_SHARED, –1, 0);
9 #elif defined(HAVE_DEV_ZERO)
10 int fd;
11 /* отображение в память файла /dev/zero */
12 if ((fd = open("/dev/zero", O_RDWR)) == –1)
13 return(MAP_FAILED);
14 shared = mmap(NULL, nbytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
15 close(fd);
16 #else
17 # error cannot determine what type of anonymous shared memory to use
18 #endif
19 return(shared); /* ошибка отображения в память */
20 }
Если система поддерживает флаг MAP_ANON (раздел 12.4), мы используем этот тип разделяемой памяти. В противном случае используется отображение в память файла /dev/zero (раздел 12.5).