【Linux编程】竞争条件

当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程进行的顺序时,则我们认为这发生了竞争条件。一个例子是在fork出子进程之后,父、子进程的执行顺序是不确定的,这种不确定决定了后面程序的结果,那么这便产生了一个竞争条件。

如果一个进程希望等待一个子进程终止,则它必须调用wait或waitpid函数。如果一个进程要等待其父进程终止,则可使用如下的轮询方式:
while (getppid() != 1)
    sleep(1);

这样的轮询需要休眠、唤醒等工作,很显然浪费了CPU资源。

如果要消除竞争条件而又不想使用轮询,则有如下两种方法:
  • 信号机制
  • 进程间通信(IPC)
这两种机制以后再讲,先来看看一个包含竞争条件的程序:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
 
static void charatatime(char *str)
{
    char *ptr;
    int c;
 
    setbuf(stdout, NULL);  /* 标准输出设置为不带缓冲 */
    for (ptr = str; (c = *ptr++) != 0; )
        putc(c, stdout);
}
 
int main(void)
{
    pid_t pid;
 
    pid = fork();
    if (pid == 0)
        charatatime("output from child\n");
    else
        charatatime("output from parent\n");
 
    return 0;
}

上述程序由fork产生了一个竞争条件。把标准输出的缓冲区大小设为了0,每输入一个字符就调用一次write系统调用,增大了进程运行时间,为的是让两个进程尽可能的切换。

运行结果:
【Linux编程】竞争条件_第1张图片

可以看到,父、子进程交替输出结果。

解决办法1:使用信号
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
 
static volatile sig_atomic_t sigflag;
static sigset_t newmask, oldmask, zeromask; /* 定义三个信号集 */
 
static void sig_usr(int signo)
{
    sigflag = 1;
}
 
void TELL_WAIT(void)
{
    if (signal(SIGUSR1, sig_usr) == SIG_ERR)
        exit(-1);
    if (signal(SIGUSR2, sig_usr) == SIG_ERR)
        exit(-1);
 
    sigemptyset(&zeromask);         /* 初始化信号集 */
    sigemptyset(&newmask);          /* 初始化信号集 */
    sigaddset(&newmask, SIGUSR1);  /* 向信号集中添加信号 */
    sigaddset(&newmask, SIGUSR2);  /* 向信号集中添加信号 */
 
    /* 更改信号屏蔽字,阻塞SIGUSR1和SIGUSR2两个信号
     * SIG_BLOCK表示新的信号屏蔽字是当前信号屏蔽字和第二个参数newmask的并集
     * oldmask保存当前的信号屏蔽字,以便稍后恢复用
     */
    if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0)
        exit(-1);
}
 
void TELL_PARENT(pid_t pid)
{
    kill(pid, SIGUSR2);
}
 
void WAIT_PARENT(void)
{
    while (sigflag == 0)
        sigsuspend(&zeromask);  /* 将信号屏蔽字设置为zeromask并在捕捉到信号之前挂起进程 */
 
    sigflag = 0;
 
    /* 恢复信号屏蔽字
     * SIG_SETMASK表示新的信号屏蔽字为由oldmask参数提供
     */
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        exit(-1);
}
 
void TELL_CHILD(pid_t pid)
{
    kill(pid, SIGUSR1);
}
 
void WAIT_CHILD(void)
{
    while (sigflag == 0)
        sigsuspend(&zeromask);
 
    sigflag = 0;
 
    if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0)
        exit(-1);
}
 
static void charatatime(char *str)
{
    char *ptr;
    int c;
  
    setbuf(stdout, NULL);  /* 标准输出设置为不带缓冲 */
    for (ptr = str; (c = *ptr++) != 0; )
        putc(c, stdout);
}
  
int main(void)
{
    pid_t pid;
    TELL_WAIT();
  
    pid = fork();
    if (pid == 0)
    {
        WAIT_PARENT();
        charatatime("output from child\n");
    }
    else
    {
        charatatime("output from parent\n");
        TELL_CHILD(pid);
        waitpid(pid, NULL, 0);
    }
  
    return 0;
}

这里使用到了信号集的概念,把若干个信号放入一个以sigset_t类型表示的信号集中,然后调用相关接口实现了父、子进程的同步。运行结果如下:


解决办法2:使用管道
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
 
static int pfd1[2], pfd2[2];
 
void TELL_WAIT(void)
{
    pipe(pfd1);     /* 建立管道1 */
    pipe(pfd2);     /* 建立管道2 */
}
 
void TELL_PARENT(pid_t pid)
{
    if (write(pfd2[1], "c", 1) != 1)   /* 写输出描述符 */
        exit(-1);
}
 
void WAIT_PARENT(void)
{
    char c;
 
    if (read(pfd1[0], &c, 1) != 1) /* 读输入描述符 */
        exit(-1);
    if (c != 'p')
        exit(-1);
}
 
void TELL_CHILD(pid_t pid)
{
    if (write(pfd1[1], "p", 1) != 1)   /* 写输出描述符 */
        exit(-1);
}
 
void WAIT_CHILD(void)
{
    char c;
 
    if (read(pfd2[0], &c, 1) != 1) /* 读输入描述符 */
        exit(-1);
    if (c != 'c')
        exit(-1);
}
 
static void charatatime(char *str)
{
    char *ptr;
    int c;
  
    setbuf(stdout, NULL);  /* 标准输出设置为不带缓冲 */
    for (ptr = str; (c = *ptr++) != 0; )
        putc(c, stdout);
}
  
int main(void)
{
    pid_t pid;
    TELL_WAIT();
  
    pid = fork();
    if (pid == 0)
    {
        WAIT_PARENT();
        charatatime("output from child\n");
    }
    else
    {
        charatatime("output from parent\n");
        TELL_CHILD(pid);
        waitpid(pid, NULL, 0);
    }
  
    return 0;
}


上述程序会建立如下进程关系:
【Linux编程】竞争条件_第2张图片

处于等待的子进程调用read函数并阻塞,等待父进程发送字符。运行结果如下:


参考:
《unix环境高级编程》 P185-P188、P256-P273、P398-P403.

你可能感兴趣的:(【Linux编程】竞争条件)