如何解决如何在 C 中异步调用函数并使其更改全局变量? 我想做什么我的尝试tl;博士
我想做什么
我正在尝试使自己成为 dwm
的模块化状态栏,例如 i3blocks
。简而言之,状态栏将由称为块的基本单元组成,每个单元代表一个命令。
例如,在下面显示的状态栏中,由箭头“
这个想法是让它们中的每一个都以一定的间隔独立更新。
我可以同步更新块,但我不想这样做,因为我的状态栏中的某些脚本需要大约 5 秒的时间来执行,这会在此期间停止其他模块,我希望避免这种情况.这在我启动 PC 时非常明显,并且状态栏上大约 5 秒没有显示任何内容,因为一个模块需要很长时间才能执行。
我的尝试
我曾尝试使用 pthread.h
,但我想放弃这种方法,因为每个模块使用一个线程似乎是个坏主意,甚至 i3blocks
也不使用它。
我使用 fork()
也只是发现我无法从子进程中修改父进程的全局变量。
我在网上研究,发现我可以使用管道让子进程与父进程通信,但我所有的努力都是徒劳的,因为使用 read()
读取管道会阻止父进程的控制流复制相同的顺序行为早点。
这是我要异步调用函数 getCommand
的代码:
void getCommands(int time) {
int pipes[len(blocks)][2];
for (int i = 0; i < len(blocks); i++) {
if (pipe(pipes[i]) == -1) {
printf("Pipe failed\n");
exit(1);
}
const Block *current = blocks + i;
if (time == 0 || (current->interval != 0 && time % current->interval == 0)) {
if (fork() == 0) {
getCommand(current,statusbar[i]);
close(pipes[i][0]);
write(pipes[i][1],statusbar[i],strlen(statusbar[i]));
close(pipes[i][1]);
exit(0);
} else {
close(pipes[i][1]);
read(pipes[i][0],CMDLENGTH);
close(pipes[i][0]);
}
}
}
}
其余代码在我的 GitHub 存储库中:UtkarshVerma/dwmblocks
tl;博士
我想知道如何使用回调对函数进行异步调用。该函数也应该能够修改全局变量。
提前致谢。
这是我第一次用 C 编写软件,因此欢迎对代码提出任何建议。
更新我想我终于取得了一些进展。这就是我正在做的事情。我在 fork 中执行命令,然后注意父级上的 SIGCHLD。在 SIGCHLD 处理程序中,我然后以非阻塞方式将所有管道读取到子叉,以检查缓冲区是否为非空。除了一个警告外,这非常有效。父级永远无法判断执行的命令是否有空白输出。我做了一个像 this 这样的 hacky 尝试,但我很好奇这是否可以更恰当地处理。
解决方法
我的建议是让您的应用程序基于不同的方法。
- 首先,限制信号的使用,它们是过时的 IPC 方法,它们可能会遇到同步问题。
- 如果您想从子进程收集数据,我认为
popen
就是您要搜索的内容。 - 正如其他同事所注意到的,我建议使用
select
或poll
来检查子输出是否准备就绪。
示例程序可能如下所示:
#include <stdio.h>
#include <fcntl.h>
#include <sys/select.h>
#include <stdbool.h>
#include <time.h>
#include <assert.h>
#define len(arr) (sizeof(arr) / sizeof(arr[0]))
typedef struct {
char *command;
unsigned int interval;
} Block;
const Block blocks[] = {
{"ls",5},{"echo abc",1}
};
int main(int argc,const char * argv[])
{
FILE* files[len(blocks)] = { NULL };
int fds[len(blocks)] = { -1,-1 };
int timeout[len(blocks)] = { 0 };
fd_set set;
int fdmax = -1;
time_t previous = time(NULL);
FD_ZERO(&set);
while (true)
{
struct timeval tm = { 1,0 };
fd_set tmp = set;
int rc = select(fdmax+1,&tmp,NULL,&tm);
//assert(rc >= 0); /* No error handling */
/* Check what's the time now,the Monotonic clock would be better */
time_t now = time(NULL);
/* Child processes loop */
for (int i = 0; i < len(blocks); ++i)
{
/* No matter if it's running or not,update counter */
timeout[i] += now - previous;
if (timeout[i]>=blocks[i].interval && fds[i]<0)
{
/* Timeout occured and the child is not running */
files[i] = popen(blocks[i].command,"r");
fds[i] = fileno(files[i]);
assert(fds[i] >= 0);
fcntl(fds[i],F_SETFL,O_NONBLOCK); /* Make sure to not block */
FD_SET(fds[i],&set); /* Add it to descriptors set */
fdmax = fdmax > fds[i] ? fdmax : fds[i];
timeout[i] = 0; /* Start interval measurement */
}
else if (fds[i]>=0 && FD_ISSET(fds[i],&tmp))
{
/* Read the pipe and consume your output */
char buffer[1024] = { 0 };
rc = fread(&buffer[0],sizeof(char),sizeof(buffer)-1,files[i]);
if (rc > 0)
printf("%s",&buffer[0]); /* Do graphics stuff */
else if (rc == 0)
{
/* Probably child has ended its job */
/* but read more about it */
rc = pclose(files[i]); /* Read exit code */
fdmax = fdmax == fds[i] ? (fdmax-1) : fdmax;
fds[i] = -1; /* Now wait for next round */
FD_CLR(fds[i],&set);
files[i] = NULL;
}
}
}
previous = now;
}
return 0;
}
更新:
此外,如果您真的需要信号来处理鼠标(我在您的代码中找到了它),您可以使用 signalfd
函数,它为信号处理创建 fd。这将限制 for 循环中的所有逻辑内容。
为每个模块创建一个数据结构,用管道连接到分叉的子节点,以易于解析的数据结构生成数据;例如,一个字母后跟数据。让孩子将更新写入管道,父母从读取端读取。
使用 fcntl(read_fd,O_NONBLOCK)
设置父描述符非阻塞。这样您就可以使用单个线程和 select()
或 poll()
作为更新描述符(可读性),并且 read()
不会阻塞。
我个人会使用一个线程来处理子进程,以上述非阻塞方式读取它们的输出管道;数据结构受互斥锁保护(仅保留最短持续时间),每个模块的更新标志由读取线程设置(每当数据更新时),并由 GUI 线程清除(每当数据更新到图形用户界面)。
,我使用 fork() 也只是发现我无法从子进程中修改父进程的全局变量。
足够公平,但您可以使用 shared 内存:
/* shm.c,compile with gcc -o shm shm.c */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#define BUFSIZE 256
int main() {
int protection = PROT_READ | PROT_WRITE;
int visibility = MAP_SHARED | MAP_ANONYMOUS;
/* use shared memory as a "hole in the wall" between parent and child: */
void *service_hatch = mmap(NULL,BUFSIZE,protection,visibility,-1,0);
if (fork()) { /* parent */
sleep(1); /* have an afternoon nap while child is cooking */
printf("we got served: %s\n",(char *) service_hatch);
} else { /* child */
/*cook up a simple meal for parent: */
strncpy(service_hatch,"French fries with mayonaise",BUFSIZE);
}
}
运行此程序时,您将看到您能够从子进程中修改父进程的全局 *service_hatch
的内容:
$ ./shm
we got served: French fries with mayonaise
编辑:为了避免在写入/读取共享内存时父子之间的竞争,请使用信号量来仲裁对它的访问:
/* shm_sem.c,compile with gcc -o shm_sem shm_sem.c -lpthread */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <semaphore.h>
#define BUFSIZE 256
/* example of a very non-atomic (i.e. piecemeal) modification of a variable */
void slowly_cook(char *meal,char *destination) {
do {
*destination++ = *meal++;
sleep(1);
} while(*meal);
}
int main() {
int protection = PROT_READ | PROT_WRITE;
int visibility = MAP_SHARED | MAP_ANONYMOUS;
/* use shared memory as a "hole in the wall" between parent and child: */
void *service_hatch = mmap(NULL,0);
/* use a semaphore,like a red/green light on the outside of the hatch */
sem_t *service_hatch_indicator = mmap(NULL,sizeof(sem_t),0);
sem_init(service_hatch_indicator,1,1);
/* use it by locking and unlocking the semaphore around each critical section */
#define CRITICAL_SECTION(sem,code) {sem_wait(sem); code; sem_post(sem);}
if (fork()) { /* parent */
sleep(1); /* have an afternoon nap while child is cooking,then wait for the light to turn green .... */
CRITICAL_SECTION(service_hatch_indicator,printf("we finally got served: %s\n",(char *) service_hatch));
} else { /* child */
/* cook up a simple meal for parent,take your time ... */
CRITICAL_SECTION(service_hatch_indicator,slowly_cook("French fries with mayonaise",service_hatch));
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。