1

我正在使用 C、pthreads 和套接字在 Linux 中编写应用程序。

这将是客户端-服务器应用程序,服务器将有 N+2 个线程,其中 N - 活动客户端的数量,一个用于接受新连接并为客户端创建线程的线程,最后一个将接受用户输入。

我将使用链表来保存一些与我的应用程序相关的数据,每个客户端都会在我的列表中关联一个节点。这些客户端线程会以一定的时间间隔更新存储在其节点中的信息,可能是一秒,也可能是两分钟,它会动态变化。

现在问题来了,如果用户请求它,存储在链表中的信息需要写入标准输出。当然,在写作期间我应该获得互斥锁。我担心整个列表的一个互斥锁会阻碍性能。

我正在考虑将互斥锁与每个节点相关联,但这会使删除某些指定节点变得复杂(首先,我需要确保“stdout writer”线程不会遍历列表,我还需要获取互斥锁我的节点和前一个节点更改指向下一个节点的指针,依此类推——要么我需要一直遍历到前一个节点,要么我需要制作双链表)。

所以我想知道涉及多个互斥锁的解决方案是否会更好,代码、条件和所有这些锁定、等待和解锁都更加复杂。

4

4 回答 4

3

您是对的,拥有每个节点的互斥锁会使代码更加复杂。这是一个权衡,你必须决定它的价值。您可以为整个列表设置一个锁,这可能会导致锁争用,但代码在很大程度上不受锁存在的影响,因此更容易编写,或者您可以拥有更多锁而竞争机会大大减少,导致更好的性能,但代码更难编写和正确。您甚至可以通过为每组节点设置一个锁来在中间拥有一些东西 - 一起分配几个节点并为该组设置一个锁 - 但是您将在跟踪空闲列表和潜在的碎片方面遇到问题。

您需要考虑添加操作、删除操作和完整列表迭代以及其他操作(重组、搜索,以及您的应用程序需要的任何其他操作)的相对频率。如果添加/删除非常频繁,但每三个蓝月亮走一次列表,那么单锁可能很合适。但是,如果遍历列表(无论是完整的数据转储,还是搜索或其他)非常普遍,那么更精细的方法会变得更有吸引力。您甚至可能需要考虑读/写锁而不是互斥锁。

于 2012-06-08T18:29:12.177 回答
1

您不需要一直遍历列表:当您遍历它时,您测试下一个元素是否是您要删除的元素,然后您可以锁定两个节点 - 在整个代码中始终以相同的顺序,因此您可以避免死锁。此外,当您需要确定互斥节点有什么时,您可以使用双重检查习惯并锁定互斥节点。

remove
    for node in list
        if node->next is the desired node
            lock(node)
            lock(node->next)
                if node->next is the desired node
                    do removing stuff
                else
                    treat concurrent modification - retry, maybe?
            release(node->next)
            release(node)

使用此习惯用法,您无需在阅读时锁定整个列表,并且还检查在第一次测试和锁定之间执行的修改。我不相信使用一组互斥锁会使代码变得更加复杂,并且与您可能执行的操作相比,锁定开销微不足道,例如 IO。

于 2012-06-08T18:35:40.203 回答
1

除非您拥有数万甚至数十万用户,否则阅读该列表不会花费那么长时间。您可能想要创建一个本地的中间列表,以便在写入时不会锁定原始列表,这可能需要一些时间。这也意味着您可以在某个时间点获得列表的快照。如果您锁定单个节点,您可以删除 A,然后删除元素 B,但当 B 没有出现时,A 会出现在显示的列表中。

据我了解,如果您确实想锁定单个节点,则您的列表必须单独链接。添加和删​​除变得相当棘手。在 Java 中,有几个系统类使用快速比较和交换技术来执行此操作。C中一定有类似的代码,但我不知道在哪里寻找它。你会得到那些按时间顺序排列的结果。

于 2012-06-08T18:51:40.123 回答
0

如果您要为 N 个活动客户端使用 N 个线程,请考虑使用 pthread_setspecific 和 pthread_getspecific 的选项。

于 2012-06-08T18:04:32.593 回答