以请求-响应案例为例,在ZeroMQ中编写多线程程序

一、使用ØMQ编写多线程程序

  • ØMQ也许是有史以来编写多线程(MT)应用程序最好的方法。为了编写完美的MT程序,除跨ØMQ套接字发送的消息外,ØMQ一般不推荐使用互斥量、锁或任何其他形式的线程间的通信
  • 这里所说的“完美”,是指代码很容易编写和理解,适用任何编程语言和任何操作系统相同的设计方法,而且可以在零等待状态按任意数量的CPU扩展,而且没有收益递减点的程序
  • 一个拥有世界一流的代码拍错经验的大公司发不了“多线程代码中可能出现的11大问题”,下面列出了7个:被遗忘的同步、不正确的粒度、读写冲突、无锁重新排序、锁护送效应、两步过程、优先级倒置
  • 根据上面的问题,ØMQ编写多线程遵循的原则是“不要共享资源与状态”

ØMQ编写多线程代码的规则

  • 不要在多个线程中访问相同的数据。使用传统的MT技术,如互斥,在ØMQ应用程序中是一种反模式。这里唯一的例外是ØMQ上下文对象,它是线程安全的
  • 你必须为你的进程创建一个ØMQ上下文,并将它传递给你想要通过inproc套接字来连接的所有线程
  • 你可以把线程作为拥有自己的上下文的独立任务,但这些线程不能通过inproc通信。不过,以后他们会更容易分解成独立的进程
  • 不要在线程之间共享ØMQ套接字。ØMQ套接字不是线程安全的。从技术上将,做到这一点是可能的,但它要求信号量、锁或互斥,浙江使得你的应用程序缓慢且脆弱。线程之间共享套接字唯一完全合乎情理的地方是“语言绑定”,它需要在套接字上做类似垃圾收集的神奇操作
  • 例如:如果你需要在应用程序中启动多个代理,你会想让每个代理在自己的线程中运行。在一个线程中创建代理前端和后端套接字,然后将套接字传递给在另一个线程的代理,这是很容易出错的。记住:不要在创建套接字的线程外使用或关闭套接字

二、编程演示案例

  • 在“请求-响应”模式的文章中(https://blog.csdn.net/qq_41453285/article/details/106878960),我们使用了“ROUTER-DEALER”套接字创建代理,来处理多个客户端与服务端进行交流,在那篇文章中我们有三个程序,如下图所示:
    • 客户端(rrclient.c):向代理发送Hello,代理将消息发送给服务端(工人),服务器(工人)给客户端回送一个“World”
    • 代理(rrbroker.c):接收客户端的消息,将消息传递给服务器(工人)
    • 服务器(工人)(rrworker.c):从代理那里接收到客户端的消息“Hello”,然后给客户端回送一条消息“World”

  • 现在我们将上面的使用多线程改写上面的“Hello-World”请求响应服务器,结构如下:
    • 客户端(rrclient.c):保持不变
    • 多线程服务器(mtserver.c):现在我们改写代理的程序rrbroker.c,将服务器程序rrworker.c的功能与rrbroker.c结合在一起,西鞥成这个多线程服务器程序mtserver.c。其中代理还是运行在主线程中,调用zmq_proxy()运行,服务器运行在pthread_create()创建的线程中

  • 注意,因为代理和服务器(工人)在线程之间进行通信,因此使用到了了inproc传输协议,关于该协议请参阅:
  • 代码如下:
// mtserver.c
// 源码链接: https://github.com/dongyusheng/csdn-code/blob/master/ZeroMQ/mtserver.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <pthread.h>
#include <zmq.h>

// 多线程运行函数, 参数为进程的上下文指针
static void *worker_routine(void *arg);

// 从socket套接字上接收消息
static char *s_recv(void *socket);

// 向socket套接字发送消息string
static int s_send(void *socket, char *string);

int main()
{
    int rc;
    
    // 1.创建新的上下文
    void *context = zmq_ctx_new();
    assert(context != NULL);
    
    // 2.创建、绑定代理的前端套接字, 客户端会来连接这个套接字
    void *clients = zmq_socket(context, ZMQ_ROUTER);
    assert(clients != NULL);
    rc = zmq_bind(clients, "tcp://*:5559");
    assert(rc != -1);
    
    // 3.创建、绑定代理后端套接字, 在pthread_create()线程中运行的工人会来连接这个套接字
    void *workers = zmq_socket(context, ZMQ_DEALER);
    assert(workers != NULL);
    rc = zmq_bind(workers, "inproc://workers");
    assert(rc != -1);

    // 4.创建4个服务器(工人), 服务器(工人)连接代理后端, 从ZMQ_DEALER端接收客户端的请求, 然后将响应返回
    int thread_nbr;
    for(thread_nbr = 0; thread_nbr < 4; ++thread_nbr)
    {
        pthread_t worker_pid;
        pthread_create(&worker_pid, NULL, worker_routine, context);
    }

    // 5.启动代理, 前端处理客户端, 后端处理服务器(工人)
    zmq_proxy(clients, workers, NULL);

    // 6.关闭套接字、销毁上下文
    zmq_close(clients);
    zmq_close(workers);
    zmq_ctx_destroy(context);
    
    return 0;
}

static void *worker_routine(void *arg)
{
    // 参数: 进程的上下文指针

    int rc;
    
    // 1.创建套接字、连接代理的ZMQ_DEALER端
    void *receiver = zmq_socket(arg, ZMQ_REP);
    assert(receiver != NULL);
    rc = zmq_connect(receiver, "inproc://workers");

    // 2.循环工作
    while(1)
    {
        // 3.从代理的ZMQ_DEALER端读取客户端的请求
        char *request = s_recv(receiver);
        printf("Received request: %s\n", request);
        free(request);

        // 4.休眠1秒再返回响应
        sleep(1);
        
        // 5.将响应发送给代理, 代理会将响应返回给客户端
        rc = s_send(receiver, "World");
        assert(rc != -1);
    }

    // 6.关闭套接字
    zmq_close(receiver);
    return NULL;
}

static char *s_recv(void *socket)
{
    int size;
    
    zmq_msg_t msg;
    zmq_msg_init(&msg);
    
    size = zmq_msg_recv(&msg, socket, 0);
    if(size == -1)
        return NULL;

    char *string = (char*)malloc(size + 1);
    if(string == NULL)
        return NULL;
    memcpy(string, zmq_msg_data(&msg), size);

    zmq_msg_close(&msg);

    string[size] = 0;

    return string;
}

static int s_send(void *socket, char *string)
{
    int rc;
    
    zmq_msg_t msg;
    zmq_msg_init_size(&msg, strlen(string));

    memcpy(zmq_msg_data(&msg), string, strlen(string));
    
    rc = zmq_msg_send(&msg, socket, 0);
    
    zmq_msg_close(&msg);

    return rc;
}
  • 代码结构如下:
    • 创建ZMQ_ROUTER套接字,客户端会来连接这个套接字
    • 创建ZMQ_DEALER套接字,服务器(工人)会来连接这个套接字
    • 程序使用pthread_create()创建一系列线程,线程中运行的是REP套接字,也就是服务器(工人),其连接上面创建的ZMQ_DEALER套接字,处理客户端的“Hello”消息,然后向客户端回送“World”
    • 主线程中调用zmq_proxy()启动代理,代理前端为ZMQ_ROUTER套接字处理的客户端,代理后端为pthread_create()创建线程中的服务器(工人)
  • 编译运行效果如下:
gcc -o mtserver mtserver.c -lzmq -lpthread

三、独家对模式:ZMQ_PAIR


  • 我是小董,V公众点击"笔记白嫖"解锁更多【ZeroMQ】资料内容。

相关推荐
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页