c语言 - 使用 pthreads 提前取消计算



我在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

    1. 锁定互斥锁
    2. 如果标志不指示完成,则更新它以指示取消
    3. 解锁互斥锁
    4. 向简历广播
  • 计算线程在完成其工作后要做的最后一件事是(锁定互斥锁)将标志设置为指示完成的值,然后广播到 CV。

  • 然后,初始线程将执行以下操作:

    1. 设置如前几点所述
    2. 锁定互斥锁
    3. 创建/启动一个间隔计时器(timer_create()),在选定的超时期限后,当它过期时引发SIGINT
    4. 启动计算线程
    5. 循环,而标志指示正在进行的计算。 在环体中
      • 对简历执行等待
    6. 此时计算已
    7. 成功完成或已取消,执行任何适当的最终操作,然后通过从main()返回或调用exit()来终止。

这仍然很干净,可以同时获得基于超时和基于键盘的取消(尽管后者使用 Ctrl-C 而不是"q"),将所有取消处理放在一个地方,除了计算线程之外只需要一个线程。

可选添加:响应"q"中止

虽然我不推荐它,但如果您真的必须通过键入"q"来终止该终止,那么您可以设置另一个线程来监视该按键/字符,并在看到它时执行raise(SIGINT)

相关内容

  • 没有找到相关文章

最新更新