C 套接字编程中使用链表的线程安全

Thread safety with a linked list in C Socket Programming

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

最简单的解决方案是在对列表进行任何访问之前锁定一个 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);

由于您的列表很少被访问,因此应该没问题。如果您 运行 遇到由争用此锁引起的可伸缩性问题,您可以转向更复杂的锁定方案。

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

在 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指针)并使用(reading/writing)存储在节点中的数据时;
  • 写操作:改变节点: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() 函数不保证 所有指定的字节数 将是 sent/received 一次函数调用。

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

此外,请注意此类结构:

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

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