MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Linux C语言线程创建的参数传递

2022-11-126.5k 阅读

1. 线程创建基础回顾

在Linux环境下使用C语言进行多线程编程,通常会用到POSIX线程库(pthread库)。创建线程的基本函数是pthread_create,其函数原型如下:

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);
  • thread:指向线程标识符的指针,也就是创建成功后该线程的ID。
  • attr:线程属性指针,一般可以设置为NULL,使用默认属性。
  • start_routine:线程启动函数的地址,线程创建成功后将执行这个函数。
  • arg:传递给线程启动函数的参数。

2. 传递简单数据类型参数

2.1 传递整型参数

假设我们的线程启动函数需要一个int类型的参数。下面是具体的代码示例:

#include <stdio.h>
#include <pthread.h>

// 线程启动函数,接受一个int类型参数
void* thread_function(void* arg) {
    int num = *((int*)arg);
    printf("线程接收到的参数: %d\n", num);
    pthread_exit(NULL);
}

int main() {
    pthread_t thread;
    int param = 42;

    // 创建线程并传递参数
    if (pthread_create(&thread, NULL, thread_function, (void*)&param) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 等待线程结束
    if (pthread_join(thread, NULL) != 0) {
        perror("pthread_join");
        return 2;
    }

    return 0;
}

在上述代码中,我们在main函数中定义了一个int类型的变量param,并将其地址作为参数传递给pthread_create函数。在线程启动函数thread_function中,通过强制类型转换将接收到的void*指针转换为int*,然后解引用获取参数值。

2.2 传递字符型参数

传递字符型参数的原理与传递整型参数类似。以下是示例代码:

#include <stdio.h>
#include <pthread.h>

// 线程启动函数,接受一个char类型参数
void* thread_function(void* arg) {
    char ch = *((char*)arg);
    printf("线程接收到的字符: %c\n", ch);
    pthread_exit(NULL);
}

int main() {
    pthread_t thread;
    char param = 'A';

    // 创建线程并传递参数
    if (pthread_create(&thread, NULL, thread_function, (void*)&param) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 等待线程结束
    if (pthread_join(thread, NULL) != 0) {
        perror("pthread_join");
        return 2;
    }

    return 0;
}

这里我们传递了一个字符型参数param,在线程启动函数中通过类似的方式获取并打印该字符。

3. 传递结构体参数

3.1 定义和传递简单结构体

当需要传递多个相关的数据时,使用结构体是一个很好的选择。假设我们有一个包含姓名和年龄的结构体:

#include <stdio.h>
#include <pthread.h>
#include <string.h>

// 定义结构体
typedef struct {
    char name[50];
    int age;
} Person;

// 线程启动函数,接受一个Person结构体指针
void* thread_function(void* arg) {
    Person* person = (Person*)arg;
    printf("姓名: %s, 年龄: %d\n", person->name, person->age);
    pthread_exit(NULL);
}

int main() {
    pthread_t thread;
    Person person = {"Alice", 30};

    // 创建线程并传递结构体参数
    if (pthread_create(&thread, NULL, thread_function, (void*)&person) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 等待线程结束
    if (pthread_join(thread, NULL) != 0) {
        perror("pthread_join");
        return 2;
    }

    return 0;
}

在上述代码中,我们定义了一个Person结构体,并在main函数中初始化了一个person变量。然后将person的地址作为参数传递给线程启动函数thread_function,在线程函数中通过强制类型转换获取结构体内容并打印。

3.2 结构体指针作为参数传递时的内存管理

需要注意的是,当传递结构体指针时,如果结构体是在栈上分配的内存(如上述示例),那么在main函数执行完后,栈上的结构体变量可能会被销毁。如果线程启动函数执行时间较长,可能会访问到无效内存。为了解决这个问题,可以使用堆内存分配结构体。

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>

// 定义结构体
typedef struct {
    char name[50];
    int age;
} Person;

// 线程启动函数,接受一个Person结构体指针
void* thread_function(void* arg) {
    Person* person = (Person*)arg;
    printf("姓名: %s, 年龄: %d\n", person->name, person->age);
    // 释放堆内存
    free(person);
    pthread_exit(NULL);
}

int main() {
    pthread_t thread;
    Person* person = (Person*)malloc(sizeof(Person));
    if (person == NULL) {
        perror("malloc");
        return 1;
    }
    strcpy(person->name, "Bob");
    person->age = 25;

    // 创建线程并传递结构体指针参数
    if (pthread_create(&thread, NULL, thread_function, (void*)person) != 0) {
        perror("pthread_create");
        free(person);
        return 1;
    }

    // 这里不能再对person进行操作,因为内存已经交给线程管理
    if (pthread_join(thread, NULL) != 0) {
        perror("pthread_join");
        return 2;
    }

    return 0;
}

在这个改进的示例中,我们使用malloc在堆上分配了Person结构体的内存,并在线程启动函数中释放了该内存,避免了内存泄漏和访问无效内存的问题。

4. 传递复杂数据结构参数

4.1 传递链表参数

链表是一种常用的复杂数据结构。假设我们有一个简单的单向链表,每个节点包含一个整数数据。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

// 定义链表节点结构体
typedef struct Node {
    int data;
    struct Node* next;
} Node;

// 定义链表结构体
typedef struct {
    Node* head;
} List;

// 创建新节点
Node* create_node(int data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    new_node->data = data;
    new_node->next = NULL;
    return new_node;
}

// 向链表头部添加节点
void add_node(List* list, int data) {
    Node* new_node = create_node(data);
    new_node->next = list->head;
    list->head = new_node;
}

// 线程启动函数,接受一个List结构体指针
void* thread_function(void* arg) {
    List* list = (List*)arg;
    Node* current = list->head;
    while (current != NULL) {
        printf("链表节点数据: %d\n", current->data);
        current = current->next;
    }
    // 释放链表内存
    current = list->head;
    Node* next;
    while (current != NULL) {
        next = current->next;
        free(current);
        current = next;
    }
    pthread_exit(NULL);
}

int main() {
    pthread_t thread;
    List list = {NULL};
    add_node(&list, 1);
    add_node(&list, 2);
    add_node(&list, 3);

    // 创建线程并传递链表参数
    if (pthread_create(&thread, NULL, thread_function, (void*)&list) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 等待线程结束
    if (pthread_join(thread, NULL) != 0) {
        perror("pthread_join");
        return 2;
    }

    return 0;
}

在上述代码中,我们定义了链表节点和链表结构体,并实现了添加节点和遍历链表的功能。在main函数中创建链表并向其中添加节点,然后将链表结构体的地址传递给线程启动函数,在线程函数中遍历链表并释放链表占用的内存。

4.2 传递树结构参数

以二叉树为例,假设我们有一个简单的二叉树结构,每个节点包含一个整数数据。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

// 定义二叉树节点结构体
typedef struct TreeNode {
    int data;
    struct TreeNode* left;
    struct TreeNode* right;
} TreeNode;

// 创建新节点
TreeNode* create_node(int data) {
    TreeNode* new_node = (TreeNode*)malloc(sizeof(TreeNode));
    new_node->data = data;
    new_node->left = NULL;
    new_node->right = NULL;
    return new_node;
}

// 插入节点到二叉树
TreeNode* insert_node(TreeNode* root, int data) {
    if (root == NULL) {
        return create_node(data);
    }
    if (data < root->data) {
        root->left = insert_node(root->left, data);
    } else {
        root->right = insert_node(root->right, data);
    }
    return root;
}

// 中序遍历二叉树
void inorder_traversal(TreeNode* root) {
    if (root != NULL) {
        inorder_traversal(root->left);
        printf("二叉树节点数据: %d\n", root->data);
        inorder_traversal(root->right);
    }
}

// 线程启动函数,接受一个TreeNode指针(二叉树的根节点)
void* thread_function(void* arg) {
    TreeNode* root = (TreeNode*)arg;
    inorder_traversal(root);
    // 释放二叉树内存(这里省略详细实现,可参考其他内存释放示例)
    pthread_exit(NULL);
}

int main() {
    pthread_t thread;
    TreeNode* root = NULL;
    root = insert_node(root, 5);
    root = insert_node(root, 3);
    root = insert_node(root, 7);

    // 创建线程并传递二叉树根节点参数
    if (pthread_create(&thread, NULL, thread_function, (void*)root) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 等待线程结束
    if (pthread_join(thread, NULL) != 0) {
        perror("pthread_join");
        return 2;
    }

    return 0;
}

在这个示例中,我们定义了二叉树节点结构体,并实现了插入节点和中序遍历的功能。在main函数中构建二叉树,然后将二叉树的根节点地址传递给线程启动函数,在线程函数中进行中序遍历并处理内存释放(这里内存释放部分简单提及,实际可根据需求详细实现)。

5. 传递函数指针参数

在一些情况下,我们可能需要在线程启动函数中执行不同的操作,这时候可以通过传递函数指针来实现。

#include <stdio.h>
#include <pthread.h>

// 定义两个不同的操作函数
void operation1() {
    printf("执行操作1\n");
}

void operation2() {
    printf("执行操作2\n");
}

// 线程启动函数,接受一个函数指针参数
void* thread_function(void* arg) {
    void (*func)() = (void (*)())arg;
    func();
    pthread_exit(NULL);
}

int main() {
    pthread_t thread1, thread2;

    // 创建第一个线程并传递operation1函数指针
    if (pthread_create(&thread1, NULL, thread_function, (void*)operation1) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 创建第二个线程并传递operation2函数指针
    if (pthread_create(&thread2, NULL, thread_function, (void*)operation2) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 等待两个线程结束
    if (pthread_join(thread1, NULL) != 0) {
        perror("pthread_join");
        return 2;
    }
    if (pthread_join(thread2, NULL) != 0) {
        perror("pthread_join");
        return 2;
    }

    return 0;
}

在上述代码中,我们定义了operation1operation2两个函数,然后在main函数中分别创建两个线程,将不同的函数指针传递给线程启动函数thread_function,从而使不同的线程执行不同的操作。

6. 多参数传递的综合解决方案

有时候需要传递多个不同类型的参数,除了使用结构体封装外,还可以通过自定义一个包含多个成员的联合体(union)来实现。

#include <stdio.h>
#include <pthread.h>

// 定义联合体用于多参数传递
union ThreadArgs {
    int int_param;
    char char_param;
    void (*func)();
};

// 线程启动函数,接受一个union ThreadArgs指针
void* thread_function(void* arg) {
    union ThreadArgs* args = (union ThreadArgs*)arg;
    printf("整型参数: %d\n", args->int_param);
    printf("字符型参数: %c\n", args->char_param);
    args->func();
    pthread_exit(NULL);
}

// 定义一个操作函数
void operation() {
    printf("执行操作\n");
}

int main() {
    pthread_t thread;
    union ThreadArgs args;
    args.int_param = 100;
    args.char_param = 'X';
    args.func = operation;

    // 创建线程并传递联合体参数
    if (pthread_create(&thread, NULL, thread_function, (void*)&args) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 等待线程结束
    if (pthread_join(thread, NULL) != 0) {
        perror("pthread_join");
        return 2;
    }

    return 0;
}

在这个示例中,我们定义了一个union ThreadArgs联合体,它可以容纳不同类型的参数。在main函数中初始化联合体的各个成员,然后将联合体的地址传递给线程启动函数,在线程函数中获取并使用这些参数。

7. 传递参数时的线程安全问题

当多个线程访问共享数据(如传递的结构体或链表中的数据)时,可能会出现线程安全问题。例如,多个线程同时修改链表可能导致链表结构损坏。为了保证线程安全,可以使用互斥锁(mutex)。

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

// 定义链表节点结构体
typedef struct Node {
    int data;
    struct Node* next;
} Node;

// 定义链表结构体
typedef struct {
    Node* head;
    pthread_mutex_t mutex;
} List;

// 创建新节点
Node* create_node(int data) {
    Node* new_node = (Node*)malloc(sizeof(Node));
    new_node->data = data;
    new_node->next = NULL;
    return new_node;
}

// 向链表头部添加节点
void add_node(List* list, int data) {
    pthread_mutex_lock(&list->mutex);
    Node* new_node = create_node(data);
    new_node->next = list->head;
    list->head = new_node;
    pthread_mutex_unlock(&list->mutex);
}

// 线程启动函数,接受一个List结构体指针
void* thread_function(void* arg) {
    List* list = (List*)arg;
    pthread_mutex_lock(&list->mutex);
    Node* current = list->head;
    while (current != NULL) {
        printf("链表节点数据: %d\n", current->data);
        current = current->next;
    }
    pthread_mutex_unlock(&list->mutex);
    pthread_exit(NULL);
}

int main() {
    pthread_t thread1, thread2;
    List list = {NULL, PTHREAD_MUTEX_INITIALIZER};

    // 线程1向链表添加节点
    if (pthread_create(&thread1, NULL, thread_function, (void*)&list) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 线程2向链表添加节点
    if (pthread_create(&thread2, NULL, thread_function, (void*)&list) != 0) {
        perror("pthread_create");
        return 1;
    }

    // 等待两个线程结束
    if (pthread_join(thread1, NULL) != 0) {
        perror("pthread_join");
        return 2;
    }
    if (pthread_join(thread2, NULL) != 0) {
        perror("pthread_join");
        return 2;
    }

    // 销毁互斥锁
    pthread_mutex_destroy(&list.mutex);

    return 0;
}

在这个示例中,我们在链表结构体中添加了一个互斥锁mutex。在向链表添加节点和遍历链表的操作中,通过pthread_mutex_lockpthread_mutex_unlock函数来保护共享数据,确保同一时间只有一个线程可以访问链表,从而避免了线程安全问题。

8. 总结传递参数的要点

  • 简单数据类型传递:直接传递其地址,在线程启动函数中进行适当的类型转换获取参数值。
  • 结构体传递:可以传递栈上分配的结构体地址,但要注意其生命周期;使用堆内存分配结构体可避免内存访问问题,同时要注意内存的分配和释放。
  • 复杂数据结构传递:如链表、树等,同样要注意内存管理和线程安全问题,使用互斥锁等机制保护共享数据。
  • 函数指针传递:通过传递函数指针,可以让线程执行不同的操作,增加程序的灵活性。
  • 多参数传递:可以使用结构体或联合体来封装多个不同类型的参数进行传递。

通过合理地传递参数,并注意线程安全和内存管理问题,我们能够更好地利用多线程的优势,编写出高效、稳定的Linux C语言程序。