我正在使用C,pthreads和套接字在Linux中编写应用程序。
这将是客户端 - 服务器应用程序,服务器将具有N + 2个线程,其中N个活动客户端,一个用于接受新连接并为客户端创建线程的线程,最后一个将接受用户输入。
我将使用链表来保存一些与我的应用程序相关的数据,每个客户端都会在我的列表中关联一个节点。那些客户端线程将以一定的间隔更新存储在其节点中的信息,可能是一秒,可能是两分钟,它将动态更改。
现在问题是,如果用户请求,则需要将存储在链表中的信息写入标准输出。当然在写作期间我应该获得互斥量。我担心整个列表中的一个互斥锁会阻碍性能。
我正在考虑将互斥锁与每个节点相关联,但它会使某些指定节点的删除变得复杂(首先,我需要确保'stdout writer'线程不会遍历列表,我还需要获取我的节点和前一个节点的互斥锁以更改指向下一个节点的指针,依此类推 - 要么我需要一直遍历到前一个节点,要么我需要制作双链表。
所以我想知道涉及多个互斥锁的解决方案是否更好用更复杂的代码,条件以及所有锁定,等待和解锁。
答案 0 :(得分:3)
你是对的,拥有每个节点的互斥锁会使代码更复杂。这是一个权衡,你必须决定价值。您可以对整个列表使用单个锁,这可能会导致锁争用,但代码在很大程度上不受锁的存在影响,因此更容易编写,或者您可以拥有更多锁,而且争用的机会更少,导致更好的性能,但代码更难编写并获得正确。你甚至可以通过每组节点锁定一些东西 - 将几个节点分配在一起并锁定该组 - 但是你会遇到跟踪空闲列表和碎片可能性的问题。 p>
您需要考虑添加操作,删除操作和完整列表迭代的相对频率,以及其他(重组,搜索,以及您的应用程序需要的任何其他内容)。如果添加/删除非常频繁,但是每隔三个蓝色月亮行走一次,单锁就很容易合适。但是,如果走在列表中(无论是对数据进行完全转储,还是搜索或其他内容)都很常见,那么更精细的方法会变得更具吸引力。您甚至可能需要考虑读取器/写入器锁而不是互斥锁。
答案 1 :(得分: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可能会执行的操作相比,锁定开销无关紧要。
答案 2 :(得分:1)
除非您有数十甚至数十万用户,否则读取列表的时间不会太长。您可能希望创建一个本地中间列表,以便在写入时不会锁定原始列表,可能需要一些时间。这也意味着您可以在某个时间点获取列表的快照。如果锁定单个节点,则可以删除A,然后删除元素B,但是当B没有时,A会出现在显示的列表中。
据我了解,如果你做想锁定单个节点,你的列表必须单独链接。添加和删除相当棘手。在Java中,有几个系统类使用快速比较和交换技术来完成此任务。在C中必须有类似的代码,但我不知道在哪里寻找它。而且你会得到那些按时间顺序排列的结果。
答案 3 :(得分:0)
如果您要为N个活动客户端设置N个线程,那么请考虑使用pthread_setspecific和pthread_getspecific的选项。