在C ++中,我有一个Stream
对象,它在Windows上抽象HANDLE
,我还有各种衍生对象,例如File
,{{ 1}},TcpSocket
,UdpSocket
直接从该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的目的是什么?
答案 0 :(得分:4)
保留仅包含未使用的RequestIo
对象的链接列表。您可以在需要时从列表的头部弹出一个对象,并在完成后将每个对象推回到列表中。
InitializeSListHead,InterlockedPushEntrySList和InterlockedPopEntrySList函数提供了一种有效的多处理器安全链表实现。