Linux下RPC多线程服务器结果异常及线程安全改造咨询
解决RPC多线程服务器中SVCXPRT句柄线程安全问题
你猜的完全没错,问题确实出在SVCXPRT传输句柄的共享上。正如你查到的文档所说,这个结构内部的解码/编码缓冲区是全局复用的,当多个线程同时操作同一个transp句柄时,后一个请求的结果会直接覆盖前一个的,这就是为什么两个客户端都拿到了60(第二个请求的计算结果)。
问题核心分析
你的ej_prg_1作为RPC框架的服务入口,把同一个transp句柄传给了不同线程。但SVCXPRT的设计从一开始就不是线程安全的——它的缓冲区是单例式的,多个线程同时读写必然会导致数据串扰,这就是结果错误的根源。
解决方案:为每个线程创建独立的传输句柄副本
要解决这个问题,核心思路是避免在多线程间共享同一个SVCXPRT实例。正确的做法是在处理请求前,为当前请求克隆一个独立的传输句柄,让每个线程操作自己的专属副本,彻底隔离数据缓冲区。
具体修改步骤如下:
- 在
ej_prg_1中调用svc_clone函数克隆当前的transp句柄,得到独立副本。 - 将克隆后的句柄传给线程,而非原始的
transp。 - 在线程处理完请求后,调用
svc_destroy销毁克隆的句柄,避免资源泄漏。
修改后的ej_prg_1代码:
static void ej_prg_1(struct svc_req *rqstp, register SVCXPRT *transp) { /* 2 threads possible for now */ pthread_t th[2]; pthread_attr_t attr[2]; static int id=0; /* Used to pass arguments to "funcionA" */ struct data_str{ struct svc_req *rqstp; SVCXPRT *transp; int id; } *data_ptr=(struct data_str*)malloc(sizeof(struct data_str)); // 克隆传输句柄,创建独立副本 SVCXPRT *cloned_transp = svc_clone(transp); if (cloned_transp == NULL) { fprintf(stderr, "Failed to clone SVCXPRT handle\n"); svcerr_systemerr(transp); free(data_ptr); return; } /* Set parameters */ data_ptr->rqstp = rqstp; data_ptr->transp = cloned_transp; // 传递克隆后的句柄 data_ptr->id = id; /* Create thread */ printf("Creating thread %d\n", id); pthread_attr_init(&attr[id]); pthread_attr_setdetachstate(&attr[id], PTHREAD_CREATE_DETACHED); pthread_create(&th[id], &attr[id], &funcionA, (void *)data_ptr); printf("Thread %d created\n", id); id=(id+1)%2; }
修改后的funcionA代码(增加资源清理):
void *funcionA(void *data) { /* Structure for parameters */ struct thr_data{ struct svc_req *rqstp; SVCXPRT *transp; int id; } *ptr_data; union { operands add_1_arg; } argument; union { int add_1_res; } result; bool_t retval; xdrproc_t _xdr_argument, _xdr_result; bool_t (*local)(char *, void *, struct svc_req *); /* Get the parameters */ ptr_data = (struct thr_data *)data; struct svc_req *rqstp = ptr_data-> rqstp; register SVCXPRT *transp = ptr_data-> transp; printf("Hello thread: %d\n", ptr_data-> id); /*Code generated by rpcgen */ switch (rqstp->rq_proc) { case NULLPROC: (void) svc_sendreply (transp, (xdrproc_t) xdr_void, (char *)NULL); goto cleanup; case ADD: _xdr_argument = (xdrproc_t) xdr_operands; _xdr_result = (xdrproc_t) xdr_int; local = (bool_t (*) (char *, void *, struct svc_req *))add_1_svc; break; default: svcerr_noproc (transp); goto cleanup; } memset ((char *)&argument, 0, sizeof (argument)); if (!svc_getargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) { svcerr_decode (transp); goto cleanup; } retval = (bool_t) (*local)((char *)&argument, (void *)&result, rqstp); if (retval > 0 && !svc_sendreply(transp, (xdrproc_t) _xdr_result, (char *)&result)) { svcerr_systemerr (transp); } if (!svc_freeargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) { fprintf (stderr, "%s", "unable to free arguments"); exit (1); } if (!ej_prg_1_freeresult (transp, _xdr_result, (caddr_t) &result)){ fprintf (stderr, "%s", "unable to free results"); } /* End of rpcgen code */ cleanup: // 销毁克隆的传输句柄 svc_destroy(ptr_data->transp); // 释放参数内存 free(ptr_data); printf("Bye thread: %d\n", ptr_data-> id); pthread_exit(0); }
额外注意事项
- 一定要检查
svc_clone的返回值,如果克隆失败,要及时向客户端返回错误并释放已分配的内存。 - 你提到的
-M标志生成的存根,在Linux下确实需要手动处理线程安全问题,因为传统RPC框架的设计没有考虑多线程并发场景,句柄隔离是最直接有效的解决方式。 - 线程中使用的
rqstp通常在请求处理期间是独立的,克隆句柄后不会有线程安全问题,但如果有额外的全局共享数据,也要注意加锁保护。
这样修改后,每个线程都操作自己独立的SVCXPRT句柄,缓冲区不会被交叉覆盖,两个客户端就能拿到各自正确的结果(40和60)了。
内容的提问来源于stack exchange,提问作者Matias
相关产品推荐
相关产品推荐

