如何中断fread调用?

6
我有以下情况:
有一个线程使用fread调用从设备读取数据。只要设备没有发送数据,此调用就会阻塞。当我停止该线程时,它仍然卡在该线程内。
现在我在fread的man页面中找到了以下内容:
错误
在符合单一UNIX规范的所有系统上,fread()函数根据以下条件设置errno:
[EINTR] 由于接收到信号而终止读取操作,并且未传输任何数据。
这意味着可以通过不同的线程来中断调用。但是我不知道如何做。有人能告诉我如何发送信号以中断fread调用吗?我需要发送什么信号?
更新08-10-10 09:25
我仍然无法使其工作。我尝试使用不同的信号进行kill()和pthread_kill()。但似乎没有任何东西可以中断fread()调用。我唯一成功的是杀死整个应用程序,但这不是我想要的。

3
有趣的是这与并发有关:我把fread误读为伦敦俚语中的“thread”(线程)。 - Jon Purdy
使用read而不是fread。 - yuanjianpeng
6个回答

10

1. 信号:

如许多人指出的那样,使用信号是可以的。但是,正如其他许多人也指出的那样,这种方法也有其缺点。

2. Select():

使用select()(或其他复用函数),您可以阻塞等待来自多个文件描述符的数据到达,并指定超时时间。

利用超时时间。每当select()返回时,请检查全局变量以查看是否必须终止。如果您想要立即响应,请继续读取。

3. Select()和管道:

多个文件描述符意味着您可以等待通过你提到的设备及管道传输的数据。

在创建线程之前,创建一个管道,然后使线程在select()上阻塞,同时监视设备和管道。每当您想要解除select()的阻塞,无论设备是否有新数据,都向管道发送一个字节。

如果select()告诉您,它因为通过管道到达数据而解除了阻塞,则可以清理并终止。请注意,此方法比信号方法更灵活,因为您不仅可以将管道用作唤醒方法,还可以将其用于传递有用信息或命令。

4. Select(),管道和信号:

如果您正在使用多个进程并且不想/无法传递管道,则可以结合两种解决方案。创建一个管道并为SIGUSR1(例如)安装信号处理程序。在信号处理程序中,向管道发送一个字节。

每当进程发送SIGUSR1时,处理程序将被调用并解除select()的阻塞。通过检查fdsets,您将知道它是因为您自己的程序发出信号而不是其他原因。


“select()”是否不够用,因为“fread()”可能会对“read()”进行多次调用?我想知道在信号处理程序中将流置于错误状态是否可行... - binki

4

您真的想了解 select(2) 系统调用,它可以让您查找是否有数据可用于该文件描述符,而不会完全阻塞或仅在该设备上进行阻塞。


虽然select()可能有用,但它并没有真正回答这个问题。 - Michael Foukarakis
2
@Michael Foukarakis - 鉴于上下文,这个问题可能在寻求解决更大问题的错误方案。John给出了一个合理的答案,通过解决更大的问题来帮助OP达到他的目标。Downvote似乎有点过分。 - bstpierre
我不知道问题是什么,所以我无法判断解决方案,对吧?也许原帖的作者必须使用 fread()。我不确定,也不想假设。 - Michael Foukarakis
fread 是有缓冲区的,所以即使有可读数据,select 也可能会无限期地阻塞。 - Mark K Cowan

3
请看man 2 kill。(或者在这里查看)
虽然我感觉你不想这样做,大多数时候人们会忽略EINTR错误并重新读取。你可能需要考虑使用非阻塞读取。

你能告诉我使用kill()系统调用需要发送哪个信号来中断fread()吗? - Peter Fortuin
一般来说,任何信号都会中断fread()调用。话虽如此,通常情况下,许多信号也会杀死您的应用程序(除了SIGCONT)。我个人会使用SIGHUP或其中一个用户定义的信号。但正如我在答案中所说,我通常也不喜欢为此目的使用信号。(请参见John Marshall的答案以获取替代方案) - Platinum Azure
2
除了使用kill从另一个线程生成信号外,alarm函数通常用于使阻塞的read调用超时。 - Ben Voigt
@Ben Voigt:是的,每次我看到它时,我仍然认为它很丑。 - ninjalj
@ninjalj:不是不同意你的观点,我更喜欢使用poll或者aio,它们拥有select所有的优点,但是没有笨重的参数设置逻辑。但了解一些模式,比如使用alarm在阻塞的I/O调用上设置超时,这样当阅读现有代码时就能够识别它们,这也是很有用的。 - Ben Voigt

2
在线程中,不要使用 fread 来阻塞,而是使用 select 进行阻塞。当 select 返回时,检查一个“我完成了吗”变量。如果没有完成,您可以调用 fread 来获取数据。
从另一个线程 - 希望停止 fread 线程的线程 - 您可以设置“我完成了吗”变量,然后关闭 fd,以便 fread 线程会立即从 select 中醒来。
如果您的上下文禁止您关闭 fd(您提到正在从设备读取,但说您想保持打开套接字),则可以打开第二个 fd,从其他线程写入以唤醒 select
如下面的评论中所建议的,关闭 fd 以唤醒 select 可能不可移植。您可以使用上述提到的第二个 fd 策略来更可移植地实现此目的。

或者你可以使用close()函数关闭传递给select()的文件描述符,以立即取消它,而不是使用上下文切换、缓存未命中、耗电轮询逻辑。 - Ben Voigt
@Ben Voigt - 感谢您指出这一点。我已相应地编辑了我的答案。 - bstpierre
@Ben:非常酷,但它是否被指定以这种方式工作?我浏览了SUS规范的select(2)和Threads部分,但没有注意到当fd在select()阻塞时从其下方消失时会看到什么描述。另一方面,我认为必须发生一些明智的事情,否则就存在潜在的漏洞利用可能性... - John Marshall
@John:看起来在这种情况下,select 应该返回 EBADF,但如果你认为这不够可移植,bstpierre 已经找到了另一种方法,稍微多做一点工作:向 select 传递第二个文件描述符,其中第二个文件描述符可以被写入(而不是关闭),以便提前唤醒线程。 - Ben Voigt
fread may do its own buffering, so select could wait indefinitely while there is data available to be read with fread - Mark K Cowan

0

除非它们被安装为中断,否则信号处理程序不会中断fread,而且未处理的信号永远不会中断。 POSIX标准允许由signal函数安装的处理程序默认为中断或非中断(在Linux上默认为非中断),因此如果您需要特定的行为,请使用sigaction函数并指定所需的sa_flags。 特别是,您需要省略SA_RESTART标志。例如:

struct sigaction sa = { .sa_handler = dummy_func, .sa_flags = 0 };
sigaction(SIGUSR1, &sa, 0);

请注意,如果省略不写,则sa_flags隐式为0,但我在初始化程序中明确包含它以说明。然后,您可以通过使用killpthread_kill发送SIGUSR1来中断fread

0

你可以使用 kill() 系统调用。

更新

事实证明我误读了你的问题。正如下面 R. 指出的那样,kill() 只能用于杀死进程,而不能用于线程。


你能告诉我使用kill()系统调用需要发送哪个信号来中断fread()吗? - Peter Fortuin
3
它必须是您之前设置了信号处理程序(参见 sigaction())并且使用SA_RESTART标志的信号。 - mark4o
1
"kill"不起作用。您需要使用"pthread_kill",它可以将信号发送到特定的线程。 - R.. GitHub STOP HELPING ICE
如果信号在其他线程中被阻塞,则调用kill()是可以的。 - mark4o

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接