我正在尝试实现一个基于UDP的服务器,该服务器维护两个套接字,一个用于控制(ctrl_sock
),另一个用于数据传输(data_sock
)。问题是,ctrl_sock
始终是上行链路,data_sock
是下行链路。也就是说,客户将通过ctrl_sock
请求数据传输/停止,数据将通过data_sock
发送给他们。
现在的问题是,由于模型是无连接的,服务器必须维护已注册客户端信息的列表(我称之为peers_context
),以便它可以“盲目地”将数据推送给他们直到他们要求停止在此盲传输期间,客户端可以异步地通过ctrl_sock
向服务器发送控制消息。除了初始请求和停止之外,这些信息还可以是例如文件部分的首选项。因此,peers_context
必须异步更新。但是,data_sock
上的传输依赖于此peers_context
结构,因此会引发ctrl_sock
和data_sock
之间的同步问题。我的问题是,我该怎么做才能安全地维护这两个袜子和peers_context
结构,以便peers_context
的异步更新不会造成严重破坏。顺便说一句,peers_context
的更新不会非常频繁,这就是我需要避免请求 - 回复模型的原因。
我对实现的初步考虑是,在主线程(监听器线程)中维护ctrl_sock,并在另一个线程(工作线程)中维护data_sock
上的传输。但是,我发现在这种情况下很难同步。例如,如果我在peers_context
中使用互斥锁,那么只要工作线程锁定peers_context
,当需要修改peers_context
时,侦听器线程将无法再访问它,因为工作者线程无休止地工作。另一方面,如果侦听器线程持有peers_context
并写入它,则工作线程将无法读取peers_context
并终止。有人可以给我一些建议吗?
顺便说一句,实现是在C环境下的Linux环境中完成的。只有监听器线程需要偶尔修改peers_context
,工作线程只需要读取。真诚的谢谢!
答案 0 :(得分:1)
如果您的peers_context
存在强烈争用,那么您需要缩短关键部分。你谈到过使用互斥锁。我假设你已经考虑过改为读者和作家锁并拒绝它,因为你不希望不断的读者饿死作家。怎么样?
制作一个非常小的结构,这是对peers_context
的间接引用,如下所示:
struct peers_context_handle {
pthread_mutex_t ref_lock;
struct peers_context *the_actual_context;
pthread_mutex_t write_lock;
};
数据包发送方(读取方)和控制请求处理方(编写方)始终通过此间接访问peers_mutex
。
假设:数据包发件人永远不会修改peers_context
,也不会释放它。
Packer发件人暂时锁定句柄,获取peers_context
的当前版本并将其解锁:
pthread_mutex_lock(&(handle->ref_lock));
peers_context = handle->the_actual_context;
pthread_mutex_unlock(&(handle->ref_lock));
(实际上,如果引入内存障碍,你甚至可以取消锁定,因为指针解除引用在Linux支持的所有平台上都是原子的,但是我不推荐它,因为你必须开始钻研内存障碍和其他低级别的东西,C和POSIX都不保证它无论如何都会起作用。)
请求处理器不更新peers_context
,它们会复制并完全替换它。这就是他们如何保持他们的关键部分小。他们做使用write_lock
来序列化更新,但更新很少,所以这不是问题。
pthread_mutex_lock(&(handle->write_lock));
/* Short CS to get the old version */
pthread_mutex_lock(&(handle->ref_lock));
old_peers_context = handle->the_actual_context;
pthread_mutex_unlock(&(handle->ref_lock));
new_peers_context = allocate_new_structure();
*new_peers_context = *old_peers_context;
/* Now make the changes that are requested */
new_peers_context->foo = 42;
new_peers_context->bar = 52;
/* Short CS to replace the context */
pthread_mutex_lock(&(handle->ref_lock));
handle->the_actual_context = new_peers_context;
pthread_mutex_unlock(&(handle->ref_lock));
pthread_mutex_unlock(&(handle->write_lock));
magic(old_peers_context);
有什么收获?这是最后一行代码中的魔力。您必须释放peers_context
的旧副本以避免内存泄漏,但您无法执行此操作,因为可能仍有数据包发件人正在使用该副本。
解决方案与Linux内核中使用的RCU类似。您必须等待所有数据包发送方线程进入静止状态。我将把这个实施作为练习留给你:-)但这里有指导方针:
magic()
函数添加old_peers_context
所以要释放的队列(必须受互斥锁保护)。peer_contexts
的点。