如何处理ZeroMQ + Ruby中的线程问题?

病毒

偶然阅读有关线程安全的ZeroMQ常见问题解答

我的多线程程序一直在ZeroMQ库中的怪异位置崩溃。我究竟做错了什么?

ZeroMQ套接字不是线程安全的。本指南对此进行了详细介绍。

简短的版本是套接字不应该在线程之间共享。我们建议为每个线程创建一个专用套接字。

对于那些无法使用每个线程专用套接字的情况,可以且仅当每个线程在访问套接字之前执行完整的内存屏障时,才可以共享套接字。大多数语言都支持Mutex或Spinlock,它们将代表您执行完整的内存屏障。

我的多线程程序一直在ZeroMQ库中的怪异位置崩溃。
我究竟做错了什么?

以下是我的以下代码:

Celluloid::ZMQ.init
module Scp
    module DataStore
    class DataSocket
        include Celluloid::ZMQ 
            def pull_socket(socket)
                @read_socket = Socket::Pull.new.tap do |read_socket|
                    ## IPC socket
                    read_socket.connect(socket)
                end
            end

            def push_socket(socket)
                @write_socket = Socket::Push.new.tap do |write_socket|
                    ## IPC socket
                    write_socket.connect(socket)
                end
            end

            def run
                pull_socket and push_socket and loopify!
            end

            def loopify!
                loop {
                   async.evaluate_response(read_socket.read_multipart)
                }
            end

            def evaluate_response(data)
                return_response(message_id,routing,Parser.parser(data))
            end

            def return_response(message_id,routing,object)
                data = object.to_response
                write_socket.send([message_id,routing,data])
            end
        end
    end
end  

DataSocket.new.run 

现在,有些事情我还不清楚:

1)假设(每次)async生成一个新的,Thread并且write_socket在所有线程和ZeroMQ之间共享,则表明它们的套接字不是线程安全的。我当然会write_socket碰到线程安全问题。
(顺便说一句,到目前为止,还没有在所有的端到端测试中都遇到这个问题。)

问题1:我的理解是否正确?

为了解决这个问题,ZeroMQ要求我们使用Mutex,Semaphore实现此目的。

结果2

2)上下文切换。

给定线程应用程序可以随时进行上下文切换。查看ffi-rzmq代码Celluloid::ZMQ .send()内部调用send_strings(),内部调用send_multiple()

问题2:上下文切换可以在内部(甚至在关键部分)(在此处)进行(在任何地方)[ https://github.com/chuckremes/ffi-rzmq/blob/master/lib/ffi-rzmq/socket.rb#L510 ]

这也可能导致数据排序问题。

我的以下观察正确吗?

注意:

Operating system ( MacOS, Linux and CentOS )  
Ruby - MRI 2.2.2/2.3.0
用户名

没有人应该将应用程序置于坚固的外壳上,而冒着应用程序健壮性的风险

原谅这个故事是一个相当长的阅读,但作者终身经验表明,原因为何比任何海上交通线少(潜在可疑或神秘,或寻找根源-无知)更为重要的尝试实验发现如何

最初的笔记

虽然ZeroMQ几十年来一直被推广为零共享(零阻塞,(几乎)零延迟和更多设计最大化)。关于优缺点的最佳阅读之处是Pieter HINTJENS的书,而不仅仅是神话般的“已连接代码,第1卷”,以及真正的社交域哲学的高级设计和工程,最近的API文档已经引入并宣传了一些恕我直言的功能,这些功能与这些分布式计算的基石原理之间有着放松的联系,那别那么尖锐地吹响“零共享”的声音。话虽如此,我仍然是一个“零共享”的家伙,因此请以这种方式查看这篇文章的其余部分。

答案1:
不,先生。-或更好-是的,不是的,先生。

ZeroMQ不会要求使用Mutex / Semaphore障碍。这与ZeroMQ设计准则背道而驰。

是的,最近的API更改开始提到(在某些附加条件下)人们可能会开始使用共享套接字……(还有(许多)附加措施)……因此含义被颠倒了。如果一个“想”,一个人也将采取所有其他步骤和措施(并支付所有最初隐藏的设计和实现成本,以“允许”共享玩具生存(希望)与其余的主要(不必要)战斗生存下来。不可控制的分布式系统环境-从而突然也承担失败的风险(出于很多明智的原因,最初的ZeroMQ零共享传福音不是这种情况)-因此,用户可以决定要走的路。 )。

完善的健壮设计IMHO仍然按照最初的ZeroMQ API和传福音进行了更好的开发,而零共享是一项原则。

答案2:
按设计总是存在关于ZeroMQ数据流排序的主要不确定性,ZeroMQ设计准则之一使设计人员不依赖于不受支持的消息排序假设,而其他许多假设(有例外情况也适用)。可以肯定的是,分发到ZeroMQ基础结构中的任何消息要么作为完整消息传递,要么根本不传递。因此,可以肯定的是,交货时不会出现零碎的沉船。有关更多详细信息,请阅读以下内容。


ThreadId不会证明任何东西(除非使用了inproc运输级)

鉴于ZeroMQ数据泵送引擎的内部设计,a的实例
zmq.Context( number_of_IO_threads )确定将产生多少个线程来处理未来的数据流。它可以在{0,1:default,2,..}的任何地方,直到几乎耗尽了内核固定的最大线程数。如果inproc://传输类实际上是直接内存区域映射的数据流处理,则值0给出了不浪费资源的合理选择(实际上从不将流量直接钉入接收器的接收板socket-abstraction:o)),并且不需要任何线程来执行此工作。
紧接着,<aSocket>.setsockopt( zmq.AFFINITY, <anIoThreadEnumID#> )允许对与数据相关的IO“液压”进行微调,以便对线程负载进行优先级,负载平衡和性能调整,以将线程负载添加到枚举的zmq.Context()-instance的IO线程,并从上面列出的设计和数据流操作方面的最佳设置中获得收益。


基石-元素是Context()s'的情况下,
不是一个Socket()的一个

一旦一个Context()的情况下得到了实例化和配置的(参见上面为什么和如何),它是(几乎)免费将要共享(如果设计不能共享抗拒或有需要避免的一个完全的建立成熟的分布式-计算基础架构)。

换句话说,大脑始终位于zmq.Context()的实例中-所有与套接字相关的dFSA引擎都在该实例中设置/配置/操作(是的,即使语法<aSocket>.setsockopt(...)的效果是在“大脑”实现-在各自zmq.Context-不是从A到B的任何电线。

最好不要共享<aSocket> (即使API-4.2.2 +承诺您可以)

到目前为止,可能已经看到了很多代码片段,其中ZeroMQ Context及其套接字被实例化并快速放置,连续只提供了几个SLOC,但是-这并不意味着除了一个非常学术的例子(由于出版商的政策,只需要印刷尽可能少的SLOC)时,实践是明智的选择,也可以通过其他任何需要进行调整。

即使在这种情况zmq.Context下,也应该基础设施设置/拆除的确实巨大的成本提出合理的警告,因此,为了避免泛化,应少用此类代码的任何副本/粘贴副本,而这些副本/粘贴副本仅用于此类说明。目的。

试想一下对于任何单个Context实例都需要进行的实际设置-准备好各个dFSA引擎的池,维护它们各自的配置设置以及所有与传输类特定的硬件相关的套接字端点池+外部O / S服务处理程序,循环事件扫描程序,缓冲区内存池分配及其动态分配器等,这些都需要时间和O / S资源,因此请明智且谨慎地处理这些(自然的)成本如果不影响性能,请调整调整后的开销。

如果仍然不确定为什么要提一下,请想象是否有人在发送数据包后坚持要拆除所有LAN电缆,并且需要等到新的电缆安装好之后才需要发送下一个数据包出现。希望现在可以更好地理解这种“合理实例化”的观点,并希望有一个共享(如果有的话)实例的论点zmq.Context(),而无需为尝试共享ZeroMQ套接字实例(即使新近成为(几乎))进行了进一步的斗争线程安全的本质)。

如果将ZeroMQ哲学用作高性能分布式计算基础架构的高级设计福音,那么它是健壮的。仅就一个(次要)方面进行调整通常不会调整所有工作和成本,因为就如何设计安全和高性能系统的全球观点而言,结果不会有任何改善(甚至是绝对可分担的风险,免费的(如果可能的话)套接字实例不会改变这一点,而声音设计,干净代码以及合理可实现的测试能力和调试的所有好处都将丢失)(如果只更改了一个细节) ,而是将另一条导线从现有的大脑中拉到这样一条新的线程上,或者为新线程配备自己的大脑,它将在本地处理它。

如果仍然有疑问,请试想一下,如果您的国家奥林匹克曲棍球队在比赛中只共享一根曲棍球棒,将会发生什么情况。或者,如果您的家乡中的所有邻居都共享同一个电话号码来接听所有来电(是的,请振铃所有电话和移动电话,同时共享同一号码),您会如何想?效果如何?


语言绑定不需要反映所有可用的API功能

在这里,有人会提出并在某些情况下是正确的,即并非所有ZeroMQ语言绑定或所有流行的框架包装器都将所有API详细信息暴露给用户以进行应用程序级编程(本文作者苦苦挣扎了很长时间)由于存在此类传统冲突,因此仍然无法解决这个原因,他不得不费劲费力才能找到任何可行的方法来解决这个问题-因此(几乎)总是可行的)


结语:

值得一提的是,ZeroMQ API 4.2.2+的最新版本开始蠕动最初提出的原则。

不过,值得记住anxient死亡象征

(添加了重点,没有大写)

线程安全

ØMQ既有线程安全套接字类型,也没有线程安全套接字类型。应用程序不得使用多个线程中的非线程安全套接字,除非将套接字从一个线程迁移到另一个具有“完全隔离”内存屏障的线程之后。

以下是线程安全套接字:* ZMQ_CLIENT* ZMQ_SERVER* ZMQ_DISH* ZMQ_RADIO* ZMQ_SCATTER*ZMQ_GATHER

尽管在某些人看来这本书听起来似乎很有前途,但在设计性能是必须的高级分布式计算系统的过程中,服务障碍是最糟糕的事情。

人们最后想看到的是阻塞自己的代码,因为这种代理进入了一种基本上不可控制的阻塞状态,在这种状态下,没有人可以从中heel脚(代理本身内部,也没有外部人员),万一远程代理永远不会传递预期的事件(在分布式系统中,由于多种原因或在无法控制的多种情况下可能发生这种情况)。

实际上,构建一个易于挂起的系统(带有受支持的(但天真地采用)语法可能性的广泛微笑)确实是一件乐事,更不用说认真的设计工作了。

在这里也不会感到惊讶,许多新的限制(最初是不可见的)适用于新方法的使用共享-{hockey-stick | 电话} API:

ZMQ_CLIENT套接字线程安全的。他们不接受不ZMQ_SNDMORE发送就不 ZMQ_RCVMORE接收选项这将它们限制为单个零件数据目的是扩展API以允许分散/收集多部分数据。

c / a

Celluloid::ZMQ不会在支持套接字类型的部分中报告任何这些新API(共享几乎原谅的罪过)套接字类型,因此没有先见可闻的好消息,而且Celluloid::ZMQ主活动似乎在2015年消失了,因此从这个角度来看,期望应该是现实的。

也就是说,在通知后可能会发现一个有趣的观点:

在使用构建您自己的分布式Celluloid系统之前,请Celluloid::ZMQ确保使DCell外观并确定其是否适合您的目的。


最后但并非最不重要的一点是,将事件循环系统组合到另一个事件循环中是一件很痛苦的事情。试图将嵌入式硬实时系统集成到另一个硬实时系统中,甚至可以从数学上证明自己是不可能的。

类似地,如果使用相同的资源,则使用另一个基于代理的组件来构建多代理系统会带来其他种类的冲突和竞争条件,这些冲突和竞争条件是从两者中(无论是有意还是通过“仅仅”产生一些功能性副作用)利用的(多个基于代理的框架。

不可挽回的相互死锁只是这些冲突中的一种,它会在无意识的设计尝试中引发最初看不见的麻烦。单代理系统设计之外的第一步使一个人失去了更多的保证,这些保证在进行多代理(分布式)之前是没有被注意到的,因此要有开放的胸怀并准备学习许多“新”概念和专心。在许多新问题上要认真注意并避免,这是一个重要的前提条件,以免(不知不觉)引入模式,这些模式现在实际上已成为分布式系统(多代理)领域中的反模式。

至少
你已经被警告过
:o)

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章