一、竞态条件
进程运行期间由于在竞争,在程序执行阶段先后顺序发生变化导致结果不一样;
1、pause函数
int pause(void);
/*
* @func:导致调用进程(或线程)进入休眠状态,直到一个信号被传递,该信号要么终止了进程,要么导致调用信号捕获函数;
* return:若信号动作是捕捉,则调用完信号处理函数后,pause返回-1;
*/
2、时序竞态
在多个进程中,若出现了优先级较高的进程,有可能会导致该程序无法按照顺序进行执行,从而导致程序卡死,报错等现象;
/*----------------------------------------------------------------------
> File Name: my_sleep.cpp
> Author: Jxiepc
> Mail: Jxiepc
> Created Time: Mon 27 Dec 2021 04:19:28 PM CST
----------------------------------------------------------------------*/
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;
void catch_sig(int signo){
cout << "catch..." << endl;
}
/**
* pause
* -----
* 自定义一个sleep函数使用pause结合alarm
* */
unsigned int my_sleep(unsigned int seconds){
/* 注册捕捉信号 */
struct sigaction act, oldact;
act.sa_handler = catch_sig;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
// 注册信号
int ret = sigaction(SIGALRM, &act, &oldact);
if(ret < 0){
cout << "si`gaction error " << endl;
exit(1);
}
// 计时发送ALRM信号
alarm(seconds);
// 将程序挂起,先处理好信号再处理程序挂起
ret = pause();
// 恢复注册信号
ret = alarm(0);
sigaction(SIGALRM, &oldact, NULL);
return ret;
}
int main(int argc, char* argv[])
{
while(1){
my_sleep(3);
cout << "----------------" << endl;
}
return 0;
}
2.1 如何解决时许竞态
可设置信号屏蔽来解决;但有可能在俩个操作期间失去cpu资源。故可将步骤合并成
原子步骤
。而sigsyspend
即可满足,且用改函数替换pause
;
int sigsuspend(const sigset_t *mask)
/*
* @func:挂起等待信号,终止进程,期间进程信号屏蔽字由mask指定;
* return:返回时进程的信号屏蔽字恢复为原来对改信号的屏蔽态,并且总是返回-1;
* 注:无法阻止SIGKILL或SIGSTOP;
*/
竞态条件跟系统负载有着紧密联系,系统
负载越严重
,出现时序混乱,信号越不可靠
;
/*----------------------------------------------------------------------
> File Name: impro_sleep.cpp
> Author: Jxiepc
> Mail: Jxiepc
> Created Time: Mon 27 Dec 2021 04:50:14 PM CST
----------------------------------------------------------------------*/
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;
void catch_sig(int signo){
cout << "catch..." << endl;
}
/**
* sigsuspend
* -----
* 自定义一个sleep函数使用sigsuspend结合alarm
* 为了解决时序竞态问题,使用pause时将程序挂起
* */
unsigned int my_sleep(unsigned int seconds){
struct sigaction act, oldact;
sigset_t newmask, oldmask, tmpmask;
// 注册信号
act.sa_handler = catch_sig;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
int ret = sigaction(SIGALRM, &act, &oldact);
if(ret < 0){
cout << "si`gaction error " << endl;
exit(1);
}
// 设置阻塞信号集
sigemptyset(&newmask);
sigaddset(&newmask, SIGALRM);
sigprocmask(SIG_BLOCK, &newmask, &oldmask);
// 计时发送ALRM信号
alarm(seconds);
// 剔除alarm信号
tmpmask = oldmask;
sigdelset(&tmpmask, SIGALRM);
/**
* 挂起等待
* 当该函数被唤醒时,恢复原由的阻塞信号集
* */
sigsuspend(&tmpmask);
// 恢复注册信号
ret = alarm(0);
sigaction(SIGALRM, &oldact, NULL);
sigprocmask(SIG_SETMASK, &oldmask, NULL);
return ret;
}
int main(int argc, char* argv[])
{
while(1){
my_sleep(3);
cout << "----------------" << endl;
}
return 0;
}
3、全局变量异步IO
尽量少用全局变量,在通过全局变量传递信号时,容易出现时许混乱,导致程序卡死;
/*----------------------------------------------------------------------
> File Name: asyn_add_num.cpp
> Author: Jxiepc
> Mail: Jxiepc
> Created Time: Mon 27 Dec 2021 07:12:53 PM CST
----------------------------------------------------------------------*/
#include <iostream>
#include <unistd.h>
#include <signal.h>
using namespace std;
/** 定义全局变量 */
int num = 0, flag = 0;
/** 子进程捕捉函数 */
void catch_sig_child(int n){
cout << "I am child num 【" << num << "】" << endl;
num += 2;
flag = 1;
sleep(1);
}
/** 父进程捕捉函数 */
void catch_sig_parent(int n){
cout << "I am parent num 【" << num << "】" << endl;
num += 2;
flag = 1;
sleep(1);
}
int main(int argc, char* argv[])
{
/* 创建子进程 */
pid_t pid ;
struct sigaction act_c, act_p;
if((pid = fork()) == -1){
cout << "fork error" << endl;
exit(1);
}else if(pid == 0){ /* 子进程 */
num = 2;
/* 注册信号 */
act_c.sa_handler = catch_sig_child;
sigemptyset(&act_c.sa_mask);
act_c.sa_flags = 0;
sigaction(SIGUSR2, &act_c, NULL);
while(1){
if(flag == 1){
kill(getppid(), SIGUSR1); // 给父进程发送信号
flag = 0;
}
}
}else { /* 父进程 */
num = 1;
/* 注册信号 */
act_p.sa_handler = catch_sig_parent;
sigemptyset(&act_p.sa_mask);
act_p.sa_flags = 0;
sigaction(SIGUSR1, &act_p, NULL);
catch_sig_parent(0);
while(1){
if(flag == 1){
kill(pid, SIGUSR2); // 给父进程发送信号
flag = 0;
}
}
}
return 0;
}
4、可/不可重入函数
某个函数在调用执行期间,由于时序混乱导致被重复调用;
可重入函数:
- 函数内不能含有全局变量以及static变量,不能使用
malloc
和free
;- 且信号捕捉函数应该设计为可重入函数;
不可重入函数:
- 使用静态数据结构;
- 调用了malloc或free;
- 是标准I/O函数;
可通过
man 7 signal
进行查看
二、SIGCHLD信号
1、产生条件
- 子进程终止时;
- 子进程接收到SIGSTOP信号停止时;
- 子进程处于停止态,接受到SIGCONT后唤醒时。
/*----------------------------------------------------------------------
> File Name: killChild.cpp
> Author: Jxiepc
> Mail: Jxiepc
> Created Time: Sat 25 Dec 2021 10:53:29 PM CST
----------------------------------------------------------------------*/
#include <iostream>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
using namespace std;
/**
* kill
* ----
* func:测试杀死指定子进程
* 参数1:pid
* 参数2:信号
* */
void testKill(){
int i;
pid_t pid, tmp_pid;
for(i=0; i<5; ++i){
pid = fork();
if(pid == 0)
break;
if(i == 2)
tmp_pid = pid;
}
/* 子进程 */
if(i < 5){
while(1){
cout << "I am child " << i << " pid = " << pid << endl;
sleep(1);
}
}else { /* 父进程 */
sleep(1);
kill(tmp_pid, SIGKILL);
while(1);
}
}
int main(int argc, char* argv[])
{
testKill();
return 0;
}
三、信号传参
1、发送信号传参
int sigqueue(pid_t pid, int sig, const union sigval value);
/*
* @func:)将sig中指定的信号发送给PID中给定的进程。 发送信号所需的权限与kill(2)相同。 和kill(2)一样,null信号(0)可以用于检查具有给定PID的进程是否存在
* @param value:
* union sigval {
int sival_int;
void *sival_ptr;
};
* return:成功返回0,错误返回-1;
*/