多线程环境中扩展的OVERLAPPED对象池:有效使用锁定的位置和方式

时间:2014-06-18 10:26:40

标签: c++ multithreading winapi atomic iocp

在C ++中,我有一个Stream对象,它在Windows上抽象HANDLE,我还有各种衍生对象,例如File,{{ 1}},TcpSocketUdpSocket直接从该Pipe对象派生,然后我还有一个Stream对象,这是我自己的扩展版本RequestIo对象,即OVERLAPPED直接继承自RequestIo结构。 从现在开始,说OVERLAPPED与说RequesIo相同。

OVERLAPPED对象中,我存储了一些无法存储在单个RequestIo结构中的有用内容,例如标志,用户指针等。 在那里,我还存储了一个指向下一个OVERLAPPED对象的指针,以便拥有这些对象的侵入式链接列表。

然后,RequestIo对象有2个这个侵入链表的头,一个用于Stream个对象用于阅读,另一个用于写入。通过这种方式,RequestIo对象可以有一些这些Stream对象的池,并且不必在每次i / o操作时分配/解除分配它们,也不需要锁定因为2侵入列表分开读取一个写作,这两种操作可能在IOCP中同时发生在2个不同的线程中。

当我有类似流的对象(例如套接字或管道)时,我将只有1 RequestIo用于阅读(不止一个不需要)和一个用于写入,所以我基本上不需要锁定,因为在第一个RequestIo分配了一个新的socket.read(),插入到链表中,它将一遍又一遍地重复使用,直到套接字被关闭并被销毁,同样写作。

但是,没有类似流的对象(例如随机访问文件,udp套接字)可以为读取或写入发出多个RequestIo。我们只考虑一个UDP套接字,它可以发出N个待处理RequestIo对象来读取数据报,或者一个随机访问文件,它可以发出几个RequestIo个数据包,用于读取/写入/从不同部分读取/写入文件。

这里事情变得复杂了。如果我有RequestIo个对象的链接列表,我实际上必须遍历该列表并查看哪个RequestIo NOT 挂起并发出一个新的i / o操作

说这样看起来很容易,但不是: 尽管我可以设置一个RequestIo的标志,表示"它的待定",但问题仍未解决:该标志不应该是原子整数吗?由于该标志将被其他一些线程取消设置。如果有多个RequestIo实例,那么检索链接列表中可用的第一个RequestIo呢?难道这也不是一个互锁的操作吗?并插入该链表?例如。当我分配一个新的RequestIo数据包,因为所有其他数据包都在等待。

我想到的一个可能的解决方案是遍历此链表并使用CAS(CompareAndSwap)指令检查RequestIo对象中的原子整数,如果为0则表示它未挂起,并立即将其设置为1,所以另一个线程将看到一个挂起,并将转到下一个RequestIo对象。如果它找不到任何RequestIo对象,它会分配一个新对象,但在这里它应该锁定链表头...以插入新分配的RequestIo对象!

那么,基本上最快,最有效的方法是正确管理N RequestIo(或我的OVERLAPPED)对象池,而不会导致大规模锁定,从而降低性能,多线程IOCP的目的是什么?

1 个答案:

答案 0 :(得分:4)

保留仅包含未使用的RequestIo对象的链接列表。您可以在需要时从列表的头部弹出一个对象,并在完成后将每个对象推回到列表中。

InitializeSListHeadInterlockedPushEntrySListInterlockedPopEntrySList函数提供了一种有效的多处理器安全链表实现。