在两个插座之间共享信息

时间:2012-04-19 18:01:00

标签: c multithreading sockets process

我正在尝试实现一个基于UDP的服务器,该服务器维护两个套接字,一个用于控制(ctrl_sock),另一个用于数据传输(data_sock)。问题是,ctrl_sock始终是上行链路,data_sock是下行链路。也就是说,客户将通过ctrl_sock请求数据传输/停止,数据将通过data_sock发送给他们。

现在的问题是,由于模型是无连接的,服务器必须维护已注册客户端信息的列表(我称之为peers_context),以便它可以“盲目地”将数据推送给他们直到他们要求停止在此盲传输期间,客户端可以异步地通过ctrl_sock向服务器发送控制消息。除了初始请求和停止之外,这些信息还可以是例如文件部分的首选项。因此,peers_context必须异步更新。但是,data_sock上的传输依赖于此peers_context结构,因此会引发ctrl_sockdata_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,工作线程只需要读取。真诚的谢谢!

1 个答案:

答案 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所以要释放的队列(必须受互斥锁保护)。
  • 一个专用线程在循环中释放此列表:
    1. 它锁定了待释放列表
    2. 获取指向列表的指针
    3. 用新的空列表替换了列表
    4. 解锁待发布的列表
    5. 清除与每个工作线程关联的标记
    6. 等待再次设置所有标记
    7. 它释放了以前获得的待释放列表副本中的每个项目
  • 同时,每个工作线程在其事件循环中的空闲点设置自己的标记(即,它不忙于发送任何数据包或持有任何peer_contexts的点。