Linux C语言线程创建的参数传递
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*)¶m) != 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*)¶m) != 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;
}
在上述代码中,我们定义了operation1
和operation2
两个函数,然后在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_lock
和pthread_mutex_unlock
函数来保护共享数据,确保同一时间只有一个线程可以访问链表,从而避免了线程安全问题。
8. 总结传递参数的要点
- 简单数据类型传递:直接传递其地址,在线程启动函数中进行适当的类型转换获取参数值。
- 结构体传递:可以传递栈上分配的结构体地址,但要注意其生命周期;使用堆内存分配结构体可避免内存访问问题,同时要注意内存的分配和释放。
- 复杂数据结构传递:如链表、树等,同样要注意内存管理和线程安全问题,使用互斥锁等机制保护共享数据。
- 函数指针传递:通过传递函数指针,可以让线程执行不同的操作,增加程序的灵活性。
- 多参数传递:可以使用结构体或联合体来封装多个不同类型的参数进行传递。
通过合理地传递参数,并注意线程安全和内存管理问题,我们能够更好地利用多线程的优势,编写出高效、稳定的Linux C语言程序。