我在c中有一个程序,我想在其中进行一些计算,这些计算可能需要也可能不需要很长时间。很难事先知道计算需要多少时间。该程序有一个cli,所以现在我通常会做这样的事情
./program
do calculation 243
它开始计算。如果我想取消它,因为它需要很多时间,我会做ctrl + c并使用另一个计算重新启动程序。现在,我希望程序在按下q或例如10秒后取消计算本身。
我找到了一种方法,它似乎可以使用 pthreads 完成我所期望的。但是,我想知道是否建议这样做,或者是否有例如任何内存泄漏或其他可能发生的事情。
以下是我的代码
void *pthread_getc(void *ptr) {
char c = ' ';
while (c != 'q')
c = getc(stdin);
pthread_cancel((pthread_t)ptr);
}
void *pthread_sleep(void *ptr) {
sleep(10);
pthread_cancel((pthread_t)ptr);
}
void pthread_cancellable(void *(*ptr)(void *), struct arg *arg) {
pthread_t thread_main, thread_getc, thread_sleep;
pthread_create(&thread_main, NULL, ptr, (void *)arg);
pthread_create(&thread_getc, NULL, pthread_getc, (void *)thread_main);
pthread_create(&thread_sleep, NULL, pthread_sleep, (void *)thread_main);
pthread_join(thread_main, NULL);
pthread_cancel(thread_getc);
pthread_cancel(thread_sleep);
pthread_join(thread_getc, NULL);
pthread_join(thread_sleep, NULL);
}
这个想法是pthread_getc和pthread_sleep都可以取消主要,一旦主要被取消,这两个也是如此。然后我简单地调用pthread_cancellable其中第一个参数是执行计算的函数,第二个参数是计算函数的参数。
这里会不会有内存泄漏或其他问题?在 c 中是否有更简单/更好的方法?
如果 main 被取消两次,如果一个线程在已经完成时被取消,会发生什么情况?
这里的内存泄漏或其他问题会出错吗?
如果程序在中止计算后要终止,则内存泄漏没有问题。 系统不依赖于进程自行清理 - 无论进程如何使用它,它都会回收分配给进程的所有内存。
但是您的代码违反了 #1pthread_cancel()
规则:永远不要调用pthread_cancel()
。 尽管监控 stdin 的q
击键是可行的,但这有点奇怪,它可能会妨碍将stdin
用于您以后想要添加到程序中的其他内容。
在 c 中是否有更简单/更好的方法?
是的。 首先,如果目标只是在超时/用户中断时终止程序,那么就这样做。 也就是说,当您要终止时,exit()
任何线程调用。 您无需为此取消任何线程。
其次,当 Ctrl-C 发送的标准中断信号工作正常时,我看不出通过实现自定义键盘操作(键入"q"中止)有什么好处,您甚至可以免费获得后者。 如果您希望或需要执行某种额外的行为来响应中断信号(在终止之前或而不是终止),请为其注册一个处理程序。
有多种方法可以实现提前终止行为,但这里有我喜欢的两种概述:
超时时无装饰的堕胎(或 Ctrl-C):
- 只需要程序的初始线程。
- 在启动计算之前,它会创建并启动一个间隔计时器(
timer_create()
)来倒计时超时。 将计时器配置为在过期时引发SIGINT
。
就是这样。 您可以通过键盘获得终止(尽管您已经使用 Ctrl-C,而不是"q"),并且在超时时外部观察者可以看到相同的终止行为。
可选添加 1:
如果需要,您可以安装一个处理程序,以便在取消时SIGINT
获得比您原本不同的行为。 但请注意,信号处理程序可以执行的操作存在重大限制。 例如,也许您想向stderr
发出一条消息(使用write()
,而不是fprintf()
用于此类事情),或者您想以非零状态exit()
而不是因为信号而终止(直接)。
可选添加 2:
如果程序达到未完成的点,但在达到超时时不再希望终止,则它可能在此时使用timer_delete()
来禁用计时器。
超时时带装饰的流产(或 Ctrl-C):
如果你想执行工作来响应不适合信号处理程序的计算中止(太多,需要调用不是异步信号安全的函数,...),那么你需要一个线程来做到这一点,以及其他控制结构和机制。 这是一种方法:
-
创建并初始化互斥锁、条件变量和
sig_atomic_t
类型的标志,所有这些都在文件范围内。 这些标志的约定是,该标志只能由当前锁定互斥锁的线程访问(读取或写入),并且互斥锁与 CV 上的所有等待相关联的互斥锁相同。 -
安装信号处理程序以
SIGINT
- 锁定互斥锁
- 如果标志不指示完成,则更新它以指示取消
- 解锁互斥锁
- 向简历广播
-
计算线程在完成其工作后要做的最后一件事是(锁定互斥锁)将标志设置为指示完成的值,然后广播到 CV。
-
然后,初始线程将执行以下操作:
- 设置如前几点所述
- 锁定互斥锁
- 创建/启动一个间隔计时器(
timer_create()
),在选定的超时期限后,当它过期时引发SIGINT
。 - 启动计算线程
- 循环,而标志指示正在进行的计算。 在环体中
- 对简历执行等待
此时计算已 - 成功完成或已取消,执行任何适当的最终操作,然后通过从
main()
返回或调用exit()
来终止。
这仍然很干净,可以同时获得基于超时和基于键盘的取消(尽管后者使用 Ctrl-C 而不是"q"),将所有取消处理放在一个地方,除了计算线程之外只需要一个线程。
可选添加:响应"q"中止
虽然我不推荐它,但如果您真的必须通过键入"q"来终止该终止,那么您可以设置另一个线程来监视该按键/字符,并在看到它时执行raise(SIGINT)
。