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,并将应答路由给请求方。