ZeroMQ_09 ZMQ多线程编程

ZMQ多线程编程基本规则:

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

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

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

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

下面我们看一个多线程的Hello word服务

Client:

#include "../zhelpers.h"
#include <stdio.h>

int main (void) 
{
    void *context = zmq_ctx_new ();

    //  Socket to talk to server
    void *requester = zmq_socket (context, ZMQ_REQ);
    zmq_connect (requester, "tcp://localhost:5555");

    int request_nbr;
    for (request_nbr = 0; request_nbr != 10; request_nbr++) {
        char strDst[256] = {0};
        snprintf(strDst,256,"Hello %d",request_nbr);
        s_send (requester, strDst);
        char *string = s_recv (requester);
        printf ("Received reply %d [%s]\n", request_nbr, string);
        free (string);
    }
    zmq_close (requester);
    zmq_ctx_destroy (context);
    return 0;
}

Server:

#include "../zhelpers.h"
#include <pthread.h>
#include <unistd.h>

#include <sys/syscall.h>

#define gettidv1() syscall(__NR_gettid)
static void *
worker_routine (void *context) {
    //  Socket to talk to dispatcher
    void *receiver = zmq_socket (context, ZMQ_REP);
    zmq_connect (receiver, "inproc://workers");

    while (1) {
        char *string = s_recv (receiver);
        printf ("[%ld]Received request: [%s]\n",(long int)gettidv1(), string);
        free (string);
        //  Do some 'work'
        sleep (1);
        //  Send reply back to client
        s_send (receiver, "World");
    }
    zmq_close (receiver);
    return NULL;
}

int main (void)
{
    void *context = zmq_ctx_new ();

    //  Socket to talk to clients
    void *clients = zmq_socket (context, ZMQ_ROUTER);
    zmq_bind (clients, "tcp://*:5555");

    //  Socket to talk to workers
    void *workers = zmq_socket (context, ZMQ_DEALER);
    zmq_bind (workers, "inproc://workers");

    //  Launch pool of worker threads
    int thread_nbr;
    for (thread_nbr = 0; thread_nbr < 5; thread_nbr++) {
        pthread_t worker;
        pthread_create (&worker, NULL, worker_routine, context);
    }
    //  Connect work threads to client threads via a queue proxy
    zmq_proxy (clients, workers, NULL);

    //  We never get here, but clean up anyhow
    zmq_close (clients);
    zmq_close (workers);
    zmq_ctx_destroy (context);
    return 0;
}

Out:

// client
Received reply 0 [World]
Received reply 1 [World]
Received reply 2 [World]
Received reply 3 [World]
Received reply 4 [World]
Received reply 5 [World]
Received reply 6 [World]
Received reply 7 [World]
Received reply 8 [World]
Received reply 9 [World]

// server
[17403]Received request: [Hello 0]
[17402]Received request: [Hello 1]
[17404]Received request: [Hello 2]
[17406]Received request: [Hello 3]
[17405]Received request: [Hello 4]
[17403]Received request: [Hello 5]
[17402]Received request: [Hello 6]
[17404]Received request: [Hello 7]
[17406]Received request: [Hello 8]
[17405]Received request: [Hello 9]

代码还是简单的。

  • 服务端启动一组worker线程,每个worker创建一个REP套接字,并处理收到的请求。worker线程就像是一个单线程的服务,唯一的区别是使用了inproc而非tcp协议,以及绑定-连接的方向调换了。
  • 服务端创建ROUTER套接字用以和client通信,因此提供了一个TCP协议的外部接口。
  • 服务端创建DEALER套接字用以和worker通信,使用了内部接口(inproc)。
  • 服务端启动了QUEUE内部装置,连接两个端点上的套接字。QUEUE装置会将收到的请求分发给连接上的worker,并将应答路由给请求方。

 

posted @ 2020-05-12 09:33  Vzf  阅读(3422)  评论(1编辑  收藏  举报