【libevent】bufferevent的并发访问问题
一、问题
在使用libevent实现websocket服务器时,发生了并发访问的问题。
服务器程序功能主要包括实时响应Websocket客户端的控制请求,同时发送温度到客户端。
现象:
不加上温度发送功能时,程序正常运行
加上温度发送功能后,就会出现段错误,而且检查后发现bufferevent并不为空
二、原因
在我的代码中,temp_cb()
用于发送温度,read_cb()
用于读取客户端发送来的数据(包括连接请求和led控制等),在没有加上温度发送功能前,程序中bufferevent
只用处理错误事件和接收数据事件,加上后还要处理发送温度的事件。如果在发送温度事件时,同时可能有其他事件或回调函数正在修改或访问相同的 bev
变量,可能会导致竞态条件或意外的状态更改,从而引发段错误,导致并发问题。
代码如下:
static void temp_cb(evutil_socket_t fd, short events, void *arg) { struct bufferevent *bev = (struct bufferevent *)arg; if (!bev) { log_error("Received NULL buffer event in temp_cb\n"); return; } send_temperature(bev); } static void read_cb (struct bufferevent *bev, void *ctx) { wss_session_t *session = bev->cbarg; if( !session->handshaked ) { do_wss_handshake(session); return ; } do_parser_frames(session); return ; } static void event_cb (struct bufferevent *bev, short events, void *ctx) { wss_session_t *session = bev->cbarg; if( events&(BEV_EVENT_EOF|BEV_EVENT_ERROR) ) { if( session ) log_warn("remote client %s closed\n", session->client); bufferevent_free(bev); } return ; } static void accept_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int len, void *arg) { struct event_base *ebase = arg; struct bufferevent *recv_bev, *send_bev; struct event *temp_event = NULL; struct timeval tv={10, 0}; struct sockaddr_in *sock = (struct sockaddr_in *)addr; wss_session_t *session; if( !(session = malloc(sizeof(*session))) ) { log_error("malloc for session failure:%s\n", strerror(errno)); close(fd); return ; } memset(session, 0, sizeof(*session)); snprintf(session->client, sizeof(session->client), "[%d->%s:%d]", fd, inet_ntoa(sock->sin_addr), ntohs(sock->sin_port)); log_info("accpet new socket client %s\n", session->client); bev_accpt = bufferevent_socket_new(ebase, fd, BEV_OPT_CLOSE_ON_FREE|BEV_OPT_DEFER_CALLBACKS); if( !bev_accpt ) { log_error("create bufferevent for client for %s failed\n", session->client); return; } session->bev = bev_accpt; bufferevent_setcb(bev_accpt, read_cb, NULL, event_cb, session); bufferevent_enable(bev_accpt, EV_READ|EV_WRITE); temp_event = event_new(ebase, -1, EV_PERSIST, temp_cb, bev_accpt); if (!temp_event) { log_error("failed to create temp event\n"); return; } event_add(temp_event, &tv); return; }
三、分析
libevent是通过I/O多路复用来实现高效的事件处理,事件循环(event loop)会不断调用底层的 I/O 多路复用函数( select
、poll
或 epoll
),等待事件的发生。
libevent本身确实是一个单线程事件驱动模型。但是也可能出现并发问题:
- 多线程环境:尽管 libevent 的核心是单线程的,但如果程序是多线程的,并且多个线程尝试访问和操作同一个
bufferevent
,就会出现并发问题。 - 事件回调重入:如果事件回调函数执行的时间较长,而在此期间另一个事件被触发并尝试访问同一个资源,可能会导致重入问题。
- 非线程安全代码:即使在单线程环境中,某些操作可能会触发不安全的并发访问,例如在回调中操作全局变量或共享资源。
四、解决方法
解决并发访问的问题最常用的方法是加锁,libevent 提供了一些机制来确保 bufferevent
在多线程环境下的安全性。例如,可以使用 bufferevent_lock
和 bufferevent_unlock
函数来显式地对 bufferevent
进行加锁和解锁操作。
本文作者:梨子Li
本文链接:https://www.cnblogs.com/LiBlog--/p/18293998
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步