【问题标题】:Thread safety with a linked list in C Socket ProgrammingC Socket 编程中使用链表的线程安全
【发布时间】:2016-01-13 06:23:08
【问题描述】:

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

【问题讨论】:

    标签: multithreading sockets linked-list thread-safety pthreads


    【解决方案1】:

    请考虑使用互斥锁来保护数据或其他资源免受并发访问。

    在 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);
    }
    

    值得注意的是,减少锁争用是优化并发应用程序性能的重要部分。

    套接字相关问题

    send()recv() 函数不保证所有指定的字节数将通过一次函数调用发送/接收。

    应使用适当的循环来发送或接收所需(预期)字节数。更多详情请参考文章:TCP/IP client-server application: exchange with string messages

    此外,请注意此类结构:

    n = recv(newsockfd, buffer, sizeof(buffer), 0); 
    // ...
    buffer[n] = '\0';
    

    要接收的预期字节数(或在本例中为字符)应为sizeof(buffer) - 1。否则,会丢失数据:最后一个字符被终止的空字符覆盖。

    【讨论】:

    • 这怎么知道要锁定哪个列表?
    • @Rachelle,互斥锁本身“不知道”要锁定哪个列表。 使用(锁定和解锁函数调用)互斥锁用于访问链表类型的一个全局实例«关联»互斥锁 使用链表类型的实例。
    【解决方案2】:

    最简单的解决方案是在对列表进行任何访问之前锁定一个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?
    • @Rachelle:我在问题的开头添加了一些内容,以说明应该在哪里声明。
    • 当我实现锁定时,它影响了我的客户端/服务器,并且不会让服务器接受任何客户端。我遇到“无效参数”错误。我很困惑这如何知道要锁定哪个列表。
    • @Rachelle:这听起来不相关。它不需要知道要锁定哪个列表 - 这只是您需要遵循的一些规则(始终在访问列表之前获取锁定)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-13
    • 1970-01-01
    • 2012-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多