在阅读有关线程安全的ZeroMQ FAQ时偶然发现。
我的多线程程序在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
答案 0 :(得分:0)
这个答案对你的问题不是一个很好的解决方案,而且肯定与user3666197的建议一致。我认为这种解决方案有可能发挥作用,但由于互斥拥塞,可能会出现大规模的性能成本。
问题1:假设异步生成新线程(每次)和write_socket在所有线程之间共享,并且zeromq说它们的套接字没有线程安全。我当然看到write_socket运行到线程安全问题。 (到目前为止,在所有端到端测试中都没有遇到过这个问题。)我的理解是否正确?
根据我对文档的理解,是的,这可能是一个问题,因为套接字不是线程安全的。即使您没有遇到此问题,也可能会在以后弹出。
问题2:上下文切换可以在任何地方发生(甚至在关键部分)
是的,所以我们可以解决这个问题的一种方法是使用互斥锁/信号量来确保我们不会在错误的时间进行上下文切换。
我会做这样的事情,但根据被调用的方法不是线程安全的,可能会有更好的方法:
Celluloid::ZMQ.init
module Scp
module DataStore
class DataSocket
include Celluloid::ZMQ
def initialize
@mutex = Mutex.new
end
def pull_socket(socket)
Thread.new do
@mutex.synchronize do
@read_socket = Socket::Pull.new.tap do |read_socket|
## IPC socket
read_socket.connect(socket)
end
end
end.join
end
def push_socket(socket)
Thread.new do
@mutex.synchronize do
@write_socket = Socket::Push.new.tap do |write_socket|
## IPC socket
write_socket.connect(socket)
end
end
end.join
end
def run
# Missing socket arguments here
pull_socket and push_socket and loopify!
end
def loopify!
Thread.new do
@mutex.synchronize do
loop {
async.evaluate_response(read_socket.read_multipart)
}
end
end.join
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