我在后台线程中使用libevent来运行hiredis并订阅远程redis数据库。使用另一个SO问题的简单示例,订阅效果非常好:
招聘人员等待消息
然而,为了避免竞争条件,在主线程中添加订阅是很重要的。为了实现这一点,我创建了一个std::vector<std::string>
对象,其中包含后端应该订阅的任何密钥字符串。从这个vector对象中读取数据是通过互斥体来完成的。
但是,我如何通知后端我已经添加了一些订阅?目前我使用的计时器设置为一个非常低的分辨率:
void Client::fireAndRequeueTimer(int fd, short e, void* arg)
{
Client* client = reinterpret_cast<Client*>(arg); // the client handles the subscription to redis (via hiredis/libevent)
if (client->mDisconnect)
return; // the main thread wants us to exit, so we don't recreate the timer
event* ev = &client->mTimerEvent; // some timer event object we created
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 1000; // 1ms
evtimer_add(ev, &tv);
// mPendingSubscriptions is an std::vector of strings, which contain the keys that we should add subscriptions to.
if (client->mPendingSubscriptions.size())
{
std::unique_lock<std::mutex> lock(client->mSubscriptionsMutex);
do
{
redisAsyncCommand(
client->mContext,
Client::subCallback,
(char*)"sub",
"SUBSCRIBE %s",
client->mPendingSubscriptions.back().c_str());
client->mPendingSubscriptions.pop_back();
}
while (client->mPendingSubscriptions.size());
}
}
(注意,我使用的是libevent 1.4.x
,所以EV_PERSIST等功能不存在,我必须在每个事件中重新创建计时器)。
虽然上面的工作,我不满意,原因如下:
- 它给后端带来了不必要的压力来持续轮询向量。
- 如果没有大量的注释,读者很难理解
- 慢;这个计时器将使订阅事件所需的时间增加1ms。这可能很重要,也可能不重要,但无论如何,这都是浪费时间。
是否有解决这个问题的方法,可以在libevent 1.4.x
的范围内解决这些问题?
我个人更喜欢让目标线程在它的事件队列中添加一个eventfd
(或类似的构造)。
eventfd
可以被任何其他线程安全地通知,并导致目标线程调用相关的事件处理程序。
这样,您就不需要担心正确锁定libevent结构的绝对最小值,因为操作系统会为您处理这个问题。
注意:eventfd
在OSX上不可用,但只要您不需要极高的事件率,可以很容易地用管道模拟。