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


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


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


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


  • 例如:如果你需要在应用程序中启动多个代理,你会想让每个代理在自己的线程中运行。在一个线程中创建代理前端和后端套接字,然后将套接字传递给在另一个线程的代理,这是很容易出错的。记住:不要在创建套接字的线程外使用或关闭套接字

二、编程演示案例

  • 在“请求-响应”模式的文章中我们使用了“ROUTER-DEALER”套接字创建代理,来处理多个客户端与服务端进行交流,在那篇文章中我们有三个程序,如下图所示:

  • 客户端(rrclient.c):向代理发送Hello,代理将消息发送给服务端(工人),服务器(工人)给客户端回送一个“World”
  • 代理(rrbroker.c):接收客户端的消息,将消息传递给服务器(工人)
  • 服务器(工人)(rrworker.c):从代理那里接收到客户端的消息“Hello”,然后给客户端回送一条消息“World”

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

  • 现在我们将上面的使用多线程改写上面的“Hello-World”请求响应服务器,结构如下:

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

以请求-响应案例为例,在ZeroMQ中编写多线程程序_服务器_02


  • 注意,因为代理和服务器(工人)在线程之间进行通信,因此使用到了了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​

以请求-响应案例为例,在ZeroMQ中编写多线程程序_使用ØMQ编写多线程程序_03


三、独家对模式:ZMQ_PAIR

  • 此模式用于跨inproc传输的线程间通信

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

以请求-响应案例为例,在ZeroMQ中编写多线程程序_套接字_04