用ZeroMQ进行多线程编程
本文翻译自ZeroMQ文档的一小部分内容, 该段落提供了解决多线程问题的一个绝佳方式, 点此查看原文.
ZeroMQ(又称为ØMQ,0MQ,或zmq)看起来像一个可嵌入的网络编程库, 但用起来是个并发框架. 它为你提供的套接字(sockets), 能以进程间, 进程内, TCP 和多播等多种方式传输原子消息. 你可以用扇出, 发布-订阅, 任务分发, 和请求-应答等多种模式, 给套接字建立多对多的连接. 它速度之快足以构建集群产品. 它的异步IO模型可供你编写作为异步消息处理任务的可扩展的多核应用. 它有几十个API, 能运行在大多数的操作系统上. ZeroMQ源自IMatix并按LGPLv3许可开放源码.
要了解关于ZeroMQ的更多信息, 请访问ZeroMQ官方网站以及阅读完整手册和API文档.
ZeroMQ有可能是编写多线程应用的最佳方式. 然而, 如果你习惯于使用传统套接字, 那么ZeroMQ套接字你需要重新适应一下, ZeroMQ多线程编程将会把你知道的关于多线程应用的一切, 统统堆到院子里, 浇上汽油, 再点一把火. 该烧的书是很少的, 但大部分关于并发编程的书都是.
要编写绝对完美的多线程程序(我就是这个意思), 除了在ZeroMQ套接字之间发送的消息之外, 我们不会使用互斥量(mutex), 锁(lock), 或者任何其他的线程间交互方式.
所谓"完美的多线程程序", 我是指这种代码: 既容易编写又容易理解; 在任何编程语言和操作系统上都使用相同的设计方法; 能以零等待状态,无损耗地扩展到任意数量的CPU.
如果你已经花费多年时间学习技巧, 只为让你的多线程代码能跑, 先不管快不快, 配合锁, 信号量(semaphore)和临界区(critical section), 一旦你发现这些全都无济于事, 你就会想唾弃它们. 在30多年的并发编程中, 如果我们只学到了一课, 那就是不要共享状态. 那就像是让两个酒鬼喝同一瓶啤酒. 他们是不是哥们无关紧要. 争抢是迟早的事. 而在桌上的酒鬼越多, 他们对酒就争抢得越厉害. 不幸的大多数多线程应用, 就像是酒吧的醉汉抢酒喝.
当你编写传统的共享状态的多线程代码的时候, 你要处理的诡异问题的列表, 要是没有直接转化成打击和风险的话, 将会很滑稽, 因为之前看起来正常的代码在压力之下突然失效了. 一个在问题代码方面有着震惊世界的经验的大公司,
发布了其名为"你的多线程代码可能有的11个问题"的列表, 其中包括忘记进行同步, 不正确的粒度, 读写割裂, 无锁重新排序, 锁护航(lock convoys), 两步舞和优先级反转.
对, 我们只举了7个问题, 而不是11个. 但这不是重点. 重点是, 你真的希望运行在电网或者股票市场上的代码在某个忙碌的周四下午3点发生锁护航吗? 谁在乎这个术语到底是什么意思呢! 我们编程不是为了干这些, 以越发复杂的黑客手段来对抗越来越复杂的副作用.
一些广泛使用的模型, 尽管是整个工业的基础, 却根本就不靠谱, 共享状态并发就是其中之一. 需要无限扩展的代码要采用互联网的做法, 发送消息, 同时除了对坏的编程模型的普遍鄙视之外, 什么也不共享.
要使用ZeroMQ写出满意的多线程代码, 你应遵循以下规则:
- 在线程中隐秘地隔离数据, 永远不要在多个线程中共享数据. 唯一的例外是ZeroMQ的contexts, 它们是线程安全的.
- 别用互斥量, 临界区, 信号量等传统的并发机制. 在ZeroMQ应用中这些都是反模式.
- 进程开始时创建一个ZeroMQ context, 并把它传递给你将用inproc套接字关联的所有线程.
- 使用 attached 线程在应用中构建结构, 并用 inproc PAIR 套接字连到父线程上. 这个模式是绑定父套接字, 然后创建会连接它的套接字子线程.
- 使用 detached 线程模拟独立任务, 带有自己的contexts. 用tcp连接它们. 以后你可以把这些转移到单独的进程而不需要修改很多代码.
- 线程间所有的交互都通过你自己多多少少正式定义的ZeroMQ消息实现.
- 不要在线程间共享ZeroMQ套接字. ZeroMQ套接字不是线程安全的. 技术上要把一个套接字从一个线程迁移到另一个线程也是可行的, 但需要技巧.
线程间共享套接字的稍微合理的唯一场景, 是在需要套接字垃圾回收之类的神奇功能的语言绑定中.
例如, 如果你需要在应用中开启一个以上的代理服务, 那么你要让每个代理运行在自己的线程中. 很容易犯的一个错误是在一个线程中创建代理的前端和后端的套接字, 然后把套接字传递给另一个线程中的代理. 这样的话, 一开始或许能工作, 但在实际使用中会出现随机的失败. 记住:不要使用或者关闭套接字,除非是在创建它们的线程里.
如果你遵循这些规则, 那么你能很容易的构建优雅的多线程应用, 并在以后需要的时候把线程拆分到单独的进程中去. 应用的逻辑可以放在线程, 进程, 或者节点中, 随便你怎么扩展.
ZeroMQ使用本地操作系统的线程, 而不是虚拟的"绿色"线程. 其优点是你不需要学习新的线程API, 并且ZeroMQ线程干净地映射到你的操作系统. 你能使用Intel的线程检查器(ThreadChecker)之类的标准工具来查看你的应用在干什么. 其缺点是本地线程API并不总是可移植的, 并且如果你线程数量很多(成千上万的), 就会对有些操作系统会造成压力.
示例和进一步信息, 请查看原文.
2017-05-25