您的位置:首页 > 运维架构 > Linux

【Linux编程】竞争条件

2014-06-10 11:20 441 查看
当多个进程都企图对共享数据进行某种处理,而最后的结果又取决于进程进行的顺序时,则我们认为这发生了竞争条件。一个例子是在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系统调用,增大了进程运行时间,为的是让两个进程尽可能的切换。

运行结果:



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

解决办法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;
}


上述程序会建立如下进程关系:



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



参考:
《unix环境高级编程》 P185-P188、P256-P273、P398-P403.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: