ZMQ之多线程编程

article/2025/9/16 5:50:07

        使用ZMQ进行多线程编程(MT编程)将会是一种享受。在多线程中使用ZMQ套接字时,你不需要考虑额外的东西,让它们自如地运作就好。

        使用ZMQ进行多线程编程时,不需要考虑互斥、锁、或其他并发程序中要考虑的因素,你唯一要关心的仅仅是线程之间的消息

        什么叫“完美”的多线程编程,指的是代码易写易读,可以跨系统、跨语言地使用同一种技术,能够在任意颗核心的计算机上运行,没有状态,没有速度的瓶颈。

        如果你有多年的多线程编程经验,知道如何使用锁、信号灯、临界区等机制来使代码运行得正确(尚未考虑快速),那你可能会很沮丧,因为ZMQ将改变这一切。三十多年来,并发式应用程序开发所总结的经验是:不要共享状态。这就好比两个醉汉想要分享一杯啤酒,如果他们不是铁哥们儿,那他们很快就会打起来。当有更多的醉汉加入时,情况就会更糟。多线程编程有时就像醉汉抢夺啤酒那样糟糕。

        进行多线程编程往往是痛苦的,当程序因为压力过大而崩溃时,你会不知所然。有人写过一篇《多线程代码中的11个错误易发点》的文章,在大公司中广为流传,列举其中的几项:没有进行同步、错误的粒度、读写分离、无锁排序、锁传递、优先级冲突等。

        假设某一天的下午三点,当证券市场正交易得如火如荼的时候,突然之间,应用程序因为锁的问题崩溃了,那将会是何等的场景?所以,作为程序员的我们,为解决那些复杂的多线程问题,只能用上更复杂的编程机制。

        有人曾这样比喻,那些多线程程序原本应作为大型公司的核心支柱,但往往又最容易出错;那些想要通过网络不断进行延伸的产品,最后总以失败告终。

        如何用ZMQ进行多线程编程,以下是一些规则:

                1、不要在不同的线程之间访问同一份数据,如果要用到传统编程中的互斥机制,那就有违ZMQ的思想了。唯一的例外是ZMQ上下文对象,它是线程安全的。

                2、必须为进程创建ZMQ上下文,并将其传递给所有你需要使用inproc协议进行通信的线程;

                3、你可以将线程作为单独的任务来对待,使用自己的上下文,但是这些线程之间就不能使用inproc协议进行通信了。这样做的好处是可以在日后方便地将程序拆分为不同的进程来运行。

                4、不要在不同的线程之间传递套接字对象,这些对象不是线程安全的。从技术上来说,你是可以这样做的,但是会用到互斥和锁的机制,这会让你的应用程序变得缓慢和脆弱。唯一合理的情形是,在某些语言的ZMQ类库内部,需要使用垃圾回收机制,这时可能会进行套接字对象的传递。

        当你需要在应用程序中使用两个装置时,可能会将套接字对象从一个线程传递给另一个线程,这样做一开始可能会成功,但最后一定会随机地发生错误。所以说,应在同一个线程中打开和关闭套接字。

        如果你能遵循上面的规则,就会发现多线程程序可以很容易地拆分成多个进程。程序逻辑可以在线程、进程、或是计算机中运行,根据你的需求进行部署即可。

        ZMQ使用的是系统原生的线程机制,而不是某种“绿色线程”。这样做的好处是你不需要学习新的多线程编程API,而且可以和目标操作系统进行很好的结合。你可以使用类似英特尔的ThreadChecker工具来查看线程工作的情况。缺点在于,如果程序创建了太多的线程(如上千个),则可能导致操作系统负载过高。

        下面我们举一个实例,让原来的Hello World服务变得更为强大。原来的服务是单线程的,如果请求较少,自然没有问题。ZMQ的线程可以在一个核心上高速地运行,执行大量的工作。但是,如果有一万次请求同时发送过来会怎么样?因此,现实环境中,我们会启动多个worker线程,他们会尽可能地接收客户端请求,处理并返回应答。

        当然,我们可以使用启动多个worker进程的方式来实现,但是启动一个进程总比启动多个进程要来的方便且易于管理。而且,作为线程启动的worker,所占用的带宽会比较少,延迟也会较低。
        以下是多线程版的Hello World服务:

        mtserver: Multithreaded service in C

#include "zhelpers.h"
#include <pthread.h>static void *
worker_routine (void *context) {//  连接至代理的套接字void *receiver = zmq_socket (context, ZMQ_REP);zmq_connect (receiver, "inproc://workers");while (1) {char *string = s_recv (receiver);printf ("Received request: [%s]\n", string);free (string);//  工作sleep (1);//  返回应答s_send (receiver, "World");}zmq_close (receiver);return NULL;
}int main (void)
{void *context = zmq_init (1);//  用于和client进行通信的套接字void *clients = zmq_socket (context, ZMQ_ROUTER);zmq_bind (clients, "tcp://*:5555");//  用于和worker进行通信的套接字void *workers = zmq_socket (context, ZMQ_DEALER);zmq_bind (workers, "inproc://workers");//  启动一个worker池int thread_nbr;for (thread_nbr = 0; thread_nbr < 5; thread_nbr++) {pthread_t worker;pthread_create (&worker, NULL, worker_routine, context);}//  启动队列装置zmq_device (ZMQ_QUEUE, clients, workers);//  程序不会运行到这里,但仍进行清理工作zmq_close (clients);zmq_close (workers);zmq_term (context);return 0;
}

        所有的代码应该都已经很熟悉了:

                1、服务端启动一组worker线程,每个worker创建一个REP套接字,并处理收到的请求。worker线程就像是一个单线程的服务,唯一的区别是使用了inproc而非tcp协议,以及绑定-连接的方向调换了。

                2、服务端创建ROUTER套接字用以和client通信,因此提供了一个TCP协议的外部接口。

                3、服务端创建DEALER套接字用以和worker通信,使用了内部接口(inproc)。

                4、服务端启动了QUEUE内部装置,连接两个端点上的套接字。QUEUE装置会将收到的请求分发给连接上的worker,并将应答路由给请求方。

        需要注意的是,在某些编程语言中,创建线程并不是特别方便,POSIX提供的类库是pthreads,但Windows中就需要使用不同的API了。我们会在第三章中讲述如何包装一个多线程编程的API。

        示例中的“工作”仅仅是1秒钟的停留,我们可以在worker中进行任意的操作,包括与其他节点进行通信。消息的流向是这样的:REQ-ROUTER-queue-DEALER-REP。

线程间的信号传输

        当你刚开始使用ZMQ进行多线程编程时,你可能会问:要如何协调两个线程的工作呢?可能会想要使用sleep()这样的方法,或者使用诸如信号、互斥等机制。事实上,你唯一要用的就是ZMQ本身。回忆一下那个醉汉抢啤酒的例子吧。

        下面的示例演示了三个线程之间需要如何进行同步:

        我们使用PAIR套接字和inproc协议。

        mtrelay: Multithreaded relay in C

#include "zhelpers.h"
#include <pthread.h>static void *
step1 (void *context) {//  连接至步骤2,告知我已就绪void *xmitter = zmq_socket (context, ZMQ_PAIR);zmq_connect (xmitter, "inproc://step2");printf ("步骤1就绪,正在通知步骤2……\n");s_send (xmitter, "READY");zmq_close (xmitter);return NULL;
}static void *
step2 (void *context) {//  启动步骤1前先绑定至inproc套接字void *receiver = zmq_socket (context, ZMQ_PAIR);zmq_bind (receiver, "inproc://step2");pthread_t thread;pthread_create (&thread, NULL, step1, context);//  等待信号char *string = s_recv (receiver);free (string);zmq_close (receiver);//  连接至步骤3,告知我已就绪void *xmitter = zmq_socket (context, ZMQ_PAIR);zmq_connect (xmitter, "inproc://step3");printf ("步骤2就绪,正在通知步骤3……\n");s_send (xmitter, "READY");zmq_close (xmitter);return NULL;
}int main (void)
{void *context = zmq_init (1);//  启动步骤2前先绑定至inproc套接字void *receiver = zmq_socket (context, ZMQ_PAIR);zmq_bind (receiver, "inproc://step3");pthread_t thread;pthread_create (&thread, NULL, step2, context);//  等待信号char *string = s_recv (receiver);free (string);zmq_close (receiver);printf ("测试成功!\n");zmq_term (context);return 0;
}

        这是一个ZMQ多线程编程的典型示例:

                1、两个线程通过inproc协议进行通信,使用同一个上下文。

                2、父线程创建一个套接字,绑定至inproc://端点,然后再启动子线程,将上下文对象传递给它。

                3、子线程创建第二个套接字,连接至inproc://端点,然后发送已就绪信号给父线程。

        需要注意的是,这段代码无法扩展到多个进程之间的协调。如果你使用inproc协议,只能建立结构非常紧密的应用程序。在延迟时间必须严格控制的情况下可以使用这种方法。对其他应用程序来说,每个线程使用同一个上下文,协议选用ipc或tcp。然后,你就可以自由地将应用程序拆分为多个进程甚至是多台计算机了。

        这是我们第一次使用PAIR套接字。为什么要使用PAIR?其他类型的套接字也可以使用,但都有一些缺点会影响到线程间的通信:

                1、你可以让信号发送方使用PUSH,接收方使用PULL,这看上去可能可以,但是需要注意的是,PUSH套接字发送消息时会进行负载均衡,如果你不小心开启了两个接收方,就会“丢失”一半的信号。而PAIR套接字建立的是一对一的连接,具有排他性。

                2、可以让发送方使用DEALER,接收方使用ROUTER。但是,ROUTER套接字会在消息的外层包裹一个来源地址,这样一来原本零字节的信号就可能要成为一个多段消息了。如果你不在乎这个问题,并且不会重复读取那个套接字,自然可以使用这种方法。但是,如果你想要使用这个套接字接收真正的数据,你就会发现ROUTER提供的消息是错误的。至于DEALER套接字,它同样有负载均衡的机制,和PUSH套接字有相同的风险。

                3、可以让发送方使用PUB,接收方使用SUB。一来消息可以照原样发送,二来PUB套接字不会进行负载均衡。但是,你需要对SUB套接字设置一个空的订阅信息(用以接收所有消息);而且,如果SUB套接字没有及时和PUB建立连接,消息很有可能会丢失。

        综上,使用PAIR套接字进行线程间的协调是最合适的。


http://chatgpt.dhexx.cn/article/GJdYB6gI.shtml

相关文章

C++之多线程编程

一、并发的实现 1.多进程并发主要解决进程间通信的问题 ①同一电脑上&#xff1a;管道、文件、消息队列、内存共享。 ②不同电脑上&#xff1a;socket网络通信。 2. 单进程中的多个线程并发&#xff08;一个主线程多个子线程实现并发&#xff09; ①一个进程中的所有线程共享内…

Springboot异步多线程编程

文章目录 一、基础知识二、什么时候用同步&异步三、什么时候需要使用多线程四、springboot异步多线程编程实现 一、基础知识 同步&#xff1a;同步就是指一个进程在执行某个请求的时候&#xff0c;若该请求需要一段时间才能返回信息&#xff0c;那么这个进程将会一直等待下…

【线程】多线程编程

目录 一、概念 1.进程与线程的区别是&#xff1f;&#xff08;常问&#xff09; 2.线程与fork系统调用 3.线程的优缺点 4.线程的实现方式 二、线程函数 1.pthread_create 2.pthread_exit 3.pthread_join 4.pthread_cancel 三、线程的使用 1.线程的基本操作 2.并发…

多线程编程及线程间通信机制

对进程线程的印象还是比较好的,这对于学习C高级的朋友是非常重要的,怎样更快的学习到线程的具体使用呢?最好的办法自然是练习再练习,然后还要看很多的代码才是,之前又听说了一个IT同行过劳死,特别的提醒广大的IT从业者,要注意合理的作息习惯,健康才是最重要的,下面结合…

多线程编程

多线程指的是一个程序中包含两个或者两个以上的线程&#xff0c;多线程的提出是为提高代码的执行效率&#xff0c;这就好比工厂中的流水线&#xff0c;只有一条称为单线程&#xff0c;有多条流水线就称为多线程。多线程提高效率的同时由于并发执行的不确定性&#xff0c;导致出…

两种 C++ 多线程编程方式,看完不懂打我...

多线程在实际编程中的重要性不言而喻&#xff0c;对 C 而言&#xff0c;当我们需要使用多线程时&#xff0c;有多种方案可供选择。比如 POSIX 线程 pthread、boost::thread 库、C11 开始支持的 std::thread 库&#xff0c;以及其他一些第三方库 libdispatch&#xff08;GCD&…

ARP攻击模拟工具

以下是本人平时的一些小作品&#xff0c;特此和大家一起分享... ARP攻击模拟工具 第一代 说明&#xff1a; 实现模拟以太网的ARP欺骗攻击和IP地址冲突攻击。 此为本人早期作品&#xff0c;固比较简陋和不完善&#xff0c;漏洞及错误之处可能较多请多多包涵&#xff01; 开发环…

ARP断网攻击

在之前的文章当中&#xff0c;我已经向大家介绍了关于ARP的欺骗原理。再进一步&#xff0c;将其中的原理运用到实战中去&#xff0c;又会有什么效果呢&#xff1f;ARP又可以给我们带来哪些具体的作用呢&#xff1f;毕竟实践要来支撑&#xff0c;我们之前学过的ARP欺骗原理。 我…

ARP-欺骗攻击

测试环境&#xff08;kali:192.168.189.7 win7:192.168.189.6 网关:192.168.189.2&#xff09; arpspoof -i eth0 -t 192.168.189.6 192.168.189.2 &#xff08;目标地址网关地址&#xff09; 接收获取的信息 随意登陆一个网站&#xff0c;可发现用户名及密码已被获取。可以…

ARP攻击原理及解决方法,很实用

故障原因】 局域网内有人使用ARP欺骗的木马程序&#xff08;比如&#xff1a;传奇盗号的软件&#xff0c;某些传奇外挂中也被恶意加载了此程序&#xff09;。 【故障原理】 要了解故障原理&#xff0c;我们先来了解一下ARP协议。 在局域网中&#xff0c;通过ARP协议来完成I…

ARP攻击怎么解决最安全

ARP攻击是指攻击者通过伪造网络中的ARP协议数据包&#xff0c;欺骗其他计算机的网络通信&#xff0c;从而实现中间人攻击等目的。 下面是ARP攻击的解决方法&#xff1a; 静态ARP表绑定MAC地址&#xff1a;在网络管理员的控制下&#xff0c;将主机的IP地址与MAC地址进行绑定&am…

ARP攻击及原理

ARP攻击原理:但凡局域网内存在arp攻击&#xff0c;说明网络存在“中间人” 1.PC1需要跟PC2通信&#xff0c;通过ARP请求包询问PC2的MAC地址&#xff0c;由于采用广播的形式&#xff0c;所以交换机会将ARP请求包从接口PC1广播到PC2和PC3。(注&#xff1a;交换机收到广播/组播/未…

ARP攻击、欺骗及防御

一、广播与广播域概述 1、广播与广播域 广播&#xff1a;将广播地址做为目的地址的数据帧。 广播域&#xff1a;网络中能接收到同一个广播所有节点的集合。 2、MAC地址广播 广播地址为FF-FF-FF-FF-FF-FF 3、IP地址广播 1&#xff09;255.255.255.255 2&#xff09;广播…

ARP攻击原理和kali实现ARP攻击

目录 一、ARP协议和ARP攻击1.ARP协议2.利用Wireshark分析ARP数据包3.ARP攻击 二、kali实现ARP攻击和ARP欺骗1.实验过程2.问题记录T_T 一、ARP协议和ARP攻击 1.ARP协议 ARP协议&#xff0c;地址解析协议&#xff08;Address Resolution Protocol&#xff09;&#xff0c;用来实…

怎么处理ARP攻击

ARP故障说明: ping 的时候可能好久才能接收到一个响应&#xff0c;或者说干脆就Ping不通。网络时好时坏&#xff0c;通过ARP -a命令不能看到同网段的在线用户&#xff0c;但是net view 可以看到全网开放共享的电脑名&#xff0c;这时候就需要注意了&#xff0c;可能是ARP攻击。…

网络安全--ARP攻击原理与防护

目录 一.ARP的原理 二.ARP攻击现象及危害 三.ARP攻击的原理 四.ARP防护 在局域网当中&#xff0c;有一个协议由于它的特性一旦遭受攻击就非常麻烦。首先是它的攻击门槛比较低&#xff0c;找到一些小工具就能实现攻击&#xff0c;而且危害极大。这个协议就是ARP协议。 ARP攻击是…

渗透技术——ARP攻击

Part 1: ARP攻击介绍 ARP&#xff08;Address Resolution Protocol&#xff0c;地址解析协议&#xff09;是一个位于TCP/IP协议栈中的底层协议&#xff0c;负责将某个IP地址解析成对应的MAC地址。而ARP攻击就是通过伪造IP地址和MAC地址实现ARP欺骗&#xff0c;能够在网络中产生…

Linux操作系统及其发行版本

文章目录 Linux是什么Liunx与WindowsLinux的发行版本1. Debian系列&#xff1a;2. Slackware系列&#xff1a;3. Redhat系列&#xff1a;4. 其他发行版本&#xff1a; Linux是什么 Linux是一套免费使用和自由传播的类Unix操作系统&#xff0c;是一个基于POSIX和UNIX的多用户、…

linux查看系统版本命令

*一、查看Linux系统发行版本 * 命令1&#xff1a;lsb_release -a 该命令适用于所有Linux系统&#xff0c;会显示出完整的版本信息&#xff0c;包括Linux系统的名称&#xff0c;如Debian、Ubuntu、CentOS等&#xff0c;和对应的版本号&#xff0c;以及该版本的代号&#xff0c…

哪些是linux系统版本,linux系统主要有哪些版本

linux系统主要版本有&#xff1a;Redhat版本&#xff0c;基于RPM包的YUM包管理方式&#xff1b;2.CentOS版本&#xff0c;免费的、开源的、可以重新分发的linux发行版&#xff1b;3.Ubuntu版本&#xff0c;拥有漂亮的用户界面的系统&#xff1b;4.Mandriva版本&#xff0c;KDE桌…