C Socket编程中链表的线程安全性

3

我正在使用线程和TCP来实现C语言中的P2P文件传输。我有一个tracker,它跟踪所有连接到它的对等方。当一个对等方第一次连接时,它将其当前目录中的文件发送到tracker,并由tracker将所有对等方的文件放入共享的链接列表中。每个对等方在tracker中都有自己的线程,当一个对等方退出时,tracker关闭该对等方的连接并从链接列表中删除该对等方的所有文件。我关心的是在添加和删除节点时锁定链接列表。我想要安全地能够从各个线程修改链接列表。我已经编写了用于添加/删除链接列表中的内容以及搜索的代码(不确定是否需要在搜索时使用锁)。如何修改我的代码以确保线程之间的链接安全?

2个回答

2
请考虑使用互斥锁来保护数据或其他资源免受并发访问。
在 POSIX 线程上下文中,互斥锁使用 pthread_mutex_t 类型(来自 sys/types.h,The Open Group Base Specifications Issue 7, IEEE Std 1003.1, 2013 Edition)。 pthread_mutex_lock()pthread_mutex_unlock() 函数(The Open Group Base Specifications Issue 7, IEEE Std 1003.1, 2013 Edition)可用于保护链表类型的实例免受并发访问,用于以下两个操作:
  • 读取操作:当枚举列表(即读取 next 指针)并使用(读取/写入)存储在节点中的数据时;
  • 写入操作:更改节点:包括 next 指针和存储在节点中的数据。
同样,相同的互斥锁可用于保护对链表头指针 head 的访问。
示例:
// ...

pthread_mutex_t list_mutex = PTHREAD_MUTEX_INITIALIZER;

// ...

void *peer_handler(void *p)
{
    // ...

    pthread_mutex_lock(&list_mutex);
    if (numNodes == 0) {
        head = newNode;
        tail = newNode;
        tail->next = NULL;
        numNodes++;
    }
    else {
        tail->next = newNode;
        tail = newNode;
        tail->next = NULL;
        numNodes++;
    }
    pthread_mutex_unlock(&list_mutex);

    // ...

    pthread_mutex_lock(&list_mutex);
    struct fileNode *ptr = head;
    while (ptr != NULL) {
        if ((strcmp(ptr->ip, temp_ip) == 0) && ptr->port == temp_port) {
            cmd = htonl(200);
            break;
        }
        ptr = ptr->next;
    }
    pthread_mutex_unlock(&list_mutex);

    // ...
}

void sendList(int newsockfd)
{
    // ...
    pthread_mutex_lock(&list_mutex);
    struct fileNode *ptr;
    ptr = head;
    while (ptr != NULL) {
        // ...

        ptr = ptr->next;

        // ...
    }
    pthread_mutex_unlock(&list_mutex);
}

void removeFiles(uint32_t port, char *client_ip)
{
    pthread_mutex_lock(&list_mutex);

    // ...

    pthread_mutex_unlock(&list_mutex);
}

void print()
{
    // ...
    pthread_mutex_lock(&list_mutex);
    struct fileNode *ptr;
    ptr = head;
    while (ptr != NULL) {
        // ...

        ptr = ptr->next;

        // ...
    }
    pthread_mutex_unlock(&list_mutex);
}

值得注意的是,减少锁争用是调整并发应用性能的重要部分。
Socket 相关问题
send() 和 recv() 函数不能保证用一个函数调用发送/接收所指定的所有字节数量
应该使用适当的循环来发送或接收所需(预期)数量的字节。有关更多详细信息,请参阅文章:TCP/IP client-server application: exchange with string messages
此外,还要小心这种构造形式:
n = recv(newsockfd, buffer, sizeof(buffer), 0); 
// ...
buffer[n] = '\0';

期望接收的字节数(或者在这种情况下,字符数)应该是sizeof(buffer) - 1。否则会出现数据丢失:最后一个字符将被覆盖为终止空字符。

这个程序如何知道要锁定哪个列表? - coder4lyf
@Rachelle,互斥锁本身“不知道”要锁定哪个列表。使用互斥锁(锁定和解锁函数调用)来访问链表类型的唯一全局实例,“将互斥锁与链表类型的实例关联起来”。 - Sergey Vyacheslavovich Brunov

0

最简单的解决方案是拥有一个pthread_mutex_t,在访问列表之前锁定它,并在完成后解锁。将其与列表变量一起声明:

struct fileNode *head, *tail;
int numNodes = 0;
pthread_mutex_t list_lock = PTHREAD_MUTEX_INITIALIZER;

例如,在添加节点时:

pthread_mutex_lock(&list_lock);
if (numNodes == 0) {
    head = newNode;
    tail = newNode;
    tail->next = NULL;
    numNodes++;
}
else {
    tail->next = newNode;
    tail = newNode;
    tail->next = NULL;
    numNodes++;
}
pthread_mutex_unlock(&list_lock);

或者在遍历列表时:

//check if peer is in the file list
pthread_mutex_lock(&list_lock);
ptr = head;
while(ptr != NULL) {
    // if peer is found
    if((strcmp(ptr->ip, temp_ip) == 0) && ptr->port == temp_port){
        cmd = htonl(200); //set cmd to 1
        break;
    }
    ptr = ptr->next;
}
pthread_mutex_unlock(&list_lock);

由于您的列表相对较少被访问,因此这应该是可以的。如果由于锁上的争用而导致可扩展性问题,则可以转移到更复杂的锁定方案。


非常感谢!我应该如何声明&list_lock? - coder4lyf
@Rachelle:我已经在问题的开头添加了一点内容,以显示应该在哪里声明。 - caf
当我实现锁定时,它影响了我的客户端/服务器,并且不允许服务器接受任何客户端。我收到了“无效参数”错误。我很困惑它如何知道要锁定哪个列表。 - coder4lyf
@Rachelle:听起来不相关。它不需要知道要锁定哪个列表 - 这只是一些规则,您需要遵循(在访问列表之前始终获取锁)。 - caf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接