2017-02-24 162 views
1

假设我已经打开开发/民意调查作为mDevPoll,是安全对我来说,同时从多个线程调用这样的代码是否可以同时从多个线程调用write()安全?

struct pollfd tmp_pfd; 
tmp_pfd.fd = fd; 
tmp_pfd.events = POLLIN; 

// Write pollfd to /dev/poll 
write(mDevPoll, &tmp_pfd, sizeof(struct pollfd)); 

...,或者我需要添加自己的同步原语周围mDevPoll

+0

我很肯定你需要做你自己的同步。对于使用多线程应用程序的I/O,我的经验是I/O不是线程安全的。 http://stackoverflow.com/questions/19974548/are-functions-in-the-c-standard-library-thread-safe有关于线程安全和C标准库的一些讨论,尽管它没有讨论'write()'特别是。然后有这篇文章,write(),线程安全和POSIX https://lwn.net/Articles/180387/ –

+2

'write()'是线程安全的。但是,是否将从多个线程到同一个文件描述符的'write()'并发原子化是另一个问题。假设写入文件不是太大,用'pwrite()'来分隔文件的大部分部分,'writev()'是可以的。 – EOF

+3

'write()'是POSIX的函数列表,如果“两个线程分别调用其中一个函数,每个调用将看到另一个调用的所有指定的效果,或者它们都不是。 http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_07 –

回答

5

Solaris 10声称符合POSIX标准。 write()函数不在the handful of system interfaces that POSIX permits to be non-thread-safe之间,所以我们可以得出结论:在Solaris 10上,从两个或更多线程同时调用write()是安全的。

POSIX在这些functions whose effects are atomic relative to each other之间运行常规文件或符号链接时也指定write()。具体来说,它说,

If two threads each call one of these functions, each call shall either see all of the specified effects of the other call, or none of them.

如果你写被定向到一个普通的文件,那么这将足以得出结论,你提出的多线程操作是安全的,在这个意义上,他们不会互相干扰,并且在一次调用中写入的数据不会与任何线程中不同调用写入的数据混杂在一起。不幸的是,/dev/poll不是一个普通的文件,所以不适用于你。

您还应该知道,write()通常不需要传输单个调用中指定的全部字节数。对于一般用途,因此必须准备通过使用循环在多个调用中传送所需的字节。 Solaris可能提供超出POSIX表达的适用保证,可能是特定于目标设备的保证,但如果没有这样的保证,可以想象其中一个线程执行部分写入,而下一个写入由另一个线程执行。这很可能不会产生你想要或预期的结果。

+1

我会在我的回答和注释中添加我注意到的:在这种情况下('write()'到'/ dev/poll'),几乎可以肯定*是安全的。而且最好是安全的,因为我的一些代码实现了OP所需的功能,并且十年前我已经提供了该代码 - 从那以后它一直工作正常。不幸的是,我不记得,也没有记录为什么我不锁定'write()'。但我打算今天晚些时候通过Illumos源代码来确定。 –

+0

Ouch。我不再那么确定 - 快速查看我的Solaris 11盒子上的'man poll.7d'显示了一个例子,在部分写入'/ dev/poll'时出错。现在我知道*我将不得不检查已发布的源代码。 –

+0

安德鲁,我会很感激您通过源代码看到的更新。尽管如此,由于我不能假定我的write()会在一次调用中完成,所以我将坚持锁定write()调用。谢谢。 – Wad

4

即使write()是完全线程安全的(禁止执行错误...),理论上这并不安全。 (强调我的): 。

The write() function shall attempt to writenbyte bytes from the buffer pointed to by buf to the file associated with the open file descriptor, fildes .

...

RETURN VALUE

Upon successful completion, these functions shall return the number of bytes actually written ...

谁也不能保证,你不会得到部分write(),所以即使每个人write()调用是原子,它不一定完整,所以你仍然可以得到交错的数据,因为它可能需要更多比拨打write()要完全写入所有数据。在实践中,如果你只做相对较小的调用write(),你可能永远不会看到局部的write(),“小”和“可能”是取决于你的实现的不确定值。

我经常发表使用与O_APPEND打开普通文件解锁单write()调用,以提高记录的性能的代码 - 建立一个日志条目,然后write()一次调用整个条目。我已经从来没有在Linux和Solaris系统上做了几乎几十年的部分或交错的write()结果,即使许多进程写入相同的日志文件。但是再次,它是一个文本日志文件,如果发生部分或交错的write(),则不会造成真正的损坏甚至数据丢失。

但是,在这种情况下,您正在将少量字节“写入”内核结构。您可以在Illumos.org上查看Solaris /dev/poll内核驱动程序源代码,并查看部分write()的可能性。我怀疑这实际上是不可能的 - 因为我回去看了十年前为我公司的软件库编写的多平台调查班。在Solaris上,它使用/dev/poll和解锁来自多个线程的write()调用。它已经工作正常了十年......

的Solaris的/ dev /泳池设备驱动程序源代码分析

的(开放)的Solaris源代码可以在这里找到:http://src.illumos.org/source/xref/illumos-gate/usr/src/uts/common/io/devpoll.c#628

dpwrite()函数是实际执行“写入”操作的/dev/poll驱动程序中的代码。我使用引号是因为它根本不是一个写操作 - 数据不会像内核中的数据那样传输,而是代表被轮询的文件描述符集被更新。

将数据从用户空间复制到内核空间 - 到使用kmem_alloc()获取的内存缓冲区。我没有看到任何可能的方式,可以是部分副本。分配成功或者不成功。在执行任何操作之前,代码可能会被中断,因为它等待对内核结构的独占访问权限write()

之后,最后返回调用是在结束 - 如果没有错误,则整个呼叫被标记为成功或任何错误的整个调用失败:

995  if (error == 0) { 
996  /* 
997  * The state of uio_resid is updated only after the pollcache 
998  * is successfully modified. 
999  */ 
1000  uioskip(uiop, copysize); 
1001 } 
1002 return (error); 
1003} 

如果你挖通的Solaris内核代码,你会看到uio_resid是成功调用后返回值为write()的结果。

因此,这个呼叫肯定看起来是全有或全无。尽管在传入多个描述符成功处理较早的描述符后,代码似乎可以在文件描述符上返回错误,但代码似乎没有返回任何部分成功指示。

如果您一次只处理一个文件描述符,我会说write()操作是完全线程安全的,并且它几乎可以确保线程安全,可以将多个文件描述符“写入”更新为驾驶员没有明显的方式返回部分write()的结果。

+1

这并不意味着写入不是线程安全的! write在标准中明确指定为线程安全的(*如果两个线程分别调用其中一个函数,则每个调用都将看到另一个调用的所有指定效果,或者它们都不*,这意味着它是线程安全的* *他们的效果没有重叠**)。写可能不会写所有要求,因为有很多的条件可能会导致部分失败(没有足够的空间等)。 –

+1

@ Jean-BaptisteYunès哦,绝对。 'write()'当然是线程安全的。问题是部分'write()'可能需要多次调用才能完全写入数据,而当它是“线程安全”时,可能会导致交错数据。在实践中,这取决于您愿意承担多大的风险,以便从简单且易于维护的代码中获得稍好的性能。我会补充一些说明。 –

+1

但在这种情况下,这不是一个单一的写!单一写入写入与其他“并发”写入是一致的,这就是要点。确保最大程度的故障安全语义将需要某种昂贵的事务语义。 –

相关问题