如何在 C 中的分叉进程上使用 POSIX 信号量?
- 2024-10-23 08:47:00
- admin 原创
- 85
问题描述:
我想分叉多个进程,然后对它们使用信号量。以下是我尝试的:
sem_init(&sem, 1, 1); /* semaphore*, pshared, value */
.
.
.
if(pid != 0){ /* parent process */
wait(NULL); /* wait all child processes */
printf("
Parent: All children have exited.
");
.
.
/* cleanup semaphores */
sem_destroy(&sem);
exit(0);
}
else{ /* child process */
sem_wait(&sem); /* P operation */
printf(" Child(%d) is in critical section.
",i);
sleep(1);
*p += i%3; /* increment *p by 0, 1 or 2 based on i */
printf(" Child(%d) new value of *p=%d.
",i,*p);
sem_post(&sem); /* V operation */
exit(0);
}
输出为:
子(0)分叉
child(1) 分叉
Child(0)处于关键部分。
Child(1)处于关键区域。
child(2) 分叉
Child(2)处于关键部分。
child(3) 分叉
Child(3)处于关键部分。
child(4) 分叉
Child(4)处于关键区域。
Child(0) 的新值 *p=0。
Child(1) 的新值 *p=1。
Child(2) 的新值 *p=3。
Child(3) 的新值 *p=3。
Child(4) *p 的新值=4。
家长:所有孩子都已经离开了。
这显然意味着信号量没有按预期工作。您能解释一下我应该如何在分叉进程上使用信号量吗?
解决方案 1:
您面临的问题是对sem_init()
功能的误解。当您阅读手册页时,
您会看到以下内容:
pshared 参数表示该信号量是否在进程的线程之间或进程之间共享。
如果您读到这里,您会认为 pshared 的非零值将使信号量成为进程间信号量。然而,这是错误的。您应该继续阅读,您将明白您必须将信号量定位在共享内存区域中。为此,可以使用几个函数,如下所示:
如果 pshared 非零,则信号量在进程之间共享,并且应位于共享内存的某个区域中(请参阅 shm_open(3)、mmap(2) 和 shmget(2))。(由于由 fork(2) 创建的子进程继承了其父进程的内存映射,因此它也可以访问信号量。)任何可以访问共享内存区域的进程都可以使用 sem_post(3)、sem_wait(3) 等对信号量进行操作。
我发现这种方法比其他方法更复杂,因此我想鼓励人们使用sem_open()
而不是sem_init()
。
下面您可以看到一个完整的程序,其说明如下:
如何在分叉进程之间分配共享内存和使用共享变量。
如何初始化共享内存区域中的信号量并供多个进程使用。
如何分叉多个进程并让父进程等待其所有子进程退出。
#include <stdio.h> /* printf() */
#include <stdlib.h> /* exit(), malloc(), free() */
#include <sys/types.h> /* key_t, sem_t, pid_t */
#include <sys/shm.h> /* shmat(), IPC_RMID */
#include <errno.h> /* errno, ECHILD */
#include <semaphore.h> /* sem_open(), sem_destroy(), sem_wait().. */
#include <fcntl.h> /* O_CREAT, O_EXEC */
int main (int argc, char **argv){
int i; /* loop variables */
key_t shmkey; /* shared memory key */
int shmid; /* shared memory id */
sem_t *sem; /* synch semaphore *//*shared */
pid_t pid; /* fork pid */
int *p; /* shared variable *//*shared */
unsigned int n; /* fork count */
unsigned int value; /* semaphore value */
/* initialize a shared variable in shared memory */
shmkey = ftok ("/dev/null", 5); /* valid directory name and a number */
printf ("shmkey for p = %d
", shmkey);
shmid = shmget (shmkey, sizeof (int), 0644 | IPC_CREAT);
if (shmid < 0){ /* shared memory error check */
perror ("shmget
");
exit (1);
}
p = (int *) shmat (shmid, NULL, 0); /* attach p to shared memory */
*p = 0;
printf ("p=%d is allocated in shared memory.
", *p);
/********************************************************/
printf ("How many children do you want to fork?
");
printf ("Fork count: ");
scanf ("%u", &n);
printf ("What do you want the semaphore value to be?
");
printf ("Semaphore value: ");
scanf ("%u", &value);
/* initialize semaphores for shared processes */
sem = sem_open ("pSem", O_CREAT | O_EXCL, 0644, value);
/* name of semaphore is "pSem", semaphore is reached using this name */
printf ("semaphores initialized.
");
/* fork child processes */
for (i = 0; i < n; i++){
pid = fork ();
if (pid < 0) {
/* check for error */
sem_unlink ("pSem");
sem_close(sem);
/* unlink prevents the semaphore existing forever */
/* if a crash occurs during the execution */
printf ("Fork error.
");
}
else if (pid == 0)
break; /* child processes */
}
/******************************************************/
/****************** PARENT PROCESS ****************/
/******************************************************/
if (pid != 0){
/* wait for all children to exit */
while (pid = waitpid (-1, NULL, 0)){
if (errno == ECHILD)
break;
}
printf ("
Parent: All children have exited.
");
/* shared memory detach */
shmdt (p);
shmctl (shmid, IPC_RMID, 0);
/* cleanup semaphores */
sem_unlink ("pSem");
sem_close(sem);
/* unlink prevents the semaphore existing forever */
/* if a crash occurs during the execution */
exit (0);
}
/******************************************************/
/****************** CHILD PROCESS *****************/
/******************************************************/
else{
sem_wait (sem); /* P operation */
printf (" Child(%d) is in critical section.
", i);
sleep (1);
*p += i % 3; /* increment *p by 0, 1 or 2 based on i */
printf (" Child(%d) new value of *p=%d.
", i, *p);
sem_post (sem); /* V operation */
exit (0);
}
}
输出
./a.out
shmkey for p = 84214791
p=0 is allocated in shared memory.
How many children do you want to fork?
Fork count: 6
What do you want the semaphore value to be?
Semaphore value: 2
semaphores initialized.
Child(0) is in critical section.
Child(1) is in critical section.
Child(0) new value of *p=0.
Child(1) new value of *p=1.
Child(2) is in critical section.
Child(3) is in critical section.
Child(2) new value of *p=3.
Child(3) new value of *p=3.
Child(4) is in critical section.
Child(5) is in critical section.
Child(4) new value of *p=4.
Child(5) new value of *p=6.
Parent: All children have exited.
检查一下还不错,shmkey
因为当ftok()
失败时,它会返回 -1。但是,如果您有多个共享变量,并且ftok()
函数多次失败,则具有shmkey
值的共享变量-1
将驻留在共享内存的同一区域中,从而导致一个变量的更改影响另一个变量。因此,程序执行将变得混乱。为了避免这种情况,最好检查是否ftok()
返回 -1(最好在源代码中检查,而不是像我一样打印到屏幕上,尽管我想向您显示关键值以防发生冲突)。
注意信号量的声明和初始化方式。它与问题(sem_t sem
vs sem_t* sem
)中所做的不同。此外,您应该按照本例中的方式使用它们。您不能sem_t*
在 中定义和使用它sem_init()
。
解决方案 2:
Linux 最小匿名sem_init
+mmap
MAP_ANONYMOUS
示例
我喜欢这种设置,因为它不会污染任何全局命名空间sem_open
。
唯一的缺点是它MAP_ANONYMOUS
不是 POSIX,而且我不知道有什么替代品:例如,匿名共享内存? 像一样采用全局标识符。shm_open
`sem_open`
主程序:
#define _GNU_SOURCE
#include <assert.h>
#include <semaphore.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char **argv) {
pid_t pid;
typedef struct {
sem_t sem;
int i;
} Semint;
Semint *semint;
size_t size = sizeof(Semint);
semint = (Semint *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, 0, 0);
assert(semint != MAP_FAILED);
/* 1: shared across processes
* 0: initial value, wait locked until one post happens (making it > 0)
*/
sem_init(&semint->sem, 1, 0);
semint->i = 0;
pid = fork();
assert(pid != -1);
if (pid == 0) {
sleep(1);
semint->i = 1;
msync(&semint->sem, size, MS_SYNC);
sem_post(&semint->sem);
exit(EXIT_SUCCESS);
}
if (argc == 1) {
sem_wait(&semint->sem);
}
/* Was modified on the other process. */
assert(semint->i == 1);
wait(NULL);
sem_destroy(&semint->sem);
assert(munmap(semint, size) != -1);
return EXIT_SUCCESS;
}
编译:
gcc -g -std=c99 -Wall -Wextra -o main main.c -lpthread
运行sem_wait
:
./main
运行无sem_wait
:
./main 1
如果没有这种同步,则assert
很可能会失败,因为孩子会睡整整一秒钟:
main: main.c:39: main: Assertion `semint->i == 1' failed.
在 Ubuntu 18.04 上测试。GitHub上游。
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理必备:盘点2024年13款好用的项目管理软件