2016 字
10 分钟

C语言线程创建与参数传递详解教程

C语言线程创建与参数传递详解教程#

前言#

在现代计算机编程中,多线程技术扮演着至关重要的角色。它允许程序同时执行多个任务,大大提高了程序的执行效率和响应能力。本文将深入探讨C语言中线程的创建过程以及如何向线程函数传递参数,帮助读者掌握这一重要的编程技能。

什么是线程?#

线程的基本概念#

线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个进程可以包含一个或多个线程,这些线程共享进程的资源,如内存空间、文件描述符等。

线程与进程的区别#

特性进程线程
资源拥有独立拥有资源共享进程资源
创建开销较大较小
通信方式复杂(IPC)简单(共享内存)
切换开销较大较小
稳定性高(相互隔离)较低(相互影响)

pthread库简介#

在Linux系统中,我们使用POSIX线程(pthread)库来进行多线程编程。pthread库提供了一套完整的API,用于线程的创建、同步、销毁等操作。

基本头文件#

#include <pthread.h> // pthread函数库
#include <stdio.h> // 标准输入输出
#include <unistd.h> // UNIX标准函数

线程创建函数pthread_create#

函数原型#

int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void*),
void *arg);

参数详解#

  1. *pthread_t thread: 指向线程标识符的指针
  2. *const pthread_attr_t attr: 线程属性,通常为NULL表示使用默认属性
  3. void *(start_routine)(void): 线程函数的指针
  4. *void arg: 传递给线程函数的参数

返回值#

  • 成功时返回0
  • 失败时返回错误码

线程参数传递机制#

为什么需要参数传递?#

在实际应用中,线程函数通常需要处理不同的数据。通过参数传递机制,我们可以将所需的数据传递给线程函数,使线程能够执行特定的任务。

参数传递的基本原理#

线程函数的参数传递遵循以下原则:

  1. 地址传递: 传递的是变量的内存地址
  2. 类型转换: 需要转换为void*类型
  3. 类型恢复: 在线程函数中需要转换回原始类型

实例解析:线程参数传递#

完整代码示例#

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
// 线程函数:处理观看电视的任务
void* watch_tv_task(void *arg)
{
// 打印参数地址
printf("接收到的参数地址: %p\n", arg);
// 将void*类型转换回int*类型
int *channel_number = (int*)arg;
// 使用转换后的指针访问数据
printf("正在观看频道: %d\n", *channel_number);
return NULL;
}
int main()
{
// 定义线程ID
pthread_t tv_thread;
// 准备要传递的参数
int channel = 10086;
printf("主函数中变量的地址: %p\n", &channel);
// 创建线程并传递参数
int result = pthread_create(&tv_thread, NULL, watch_tv_task, (void*)&channel);
if (result != 0) {
printf("线程创建失败!\n");
return 1;
}
// 等待线程执行(实际应用中应该使用pthread_join)
sleep(1);
printf("主函数继续执行...\n");
return 0;
}

代码详细解析#

1. 线程函数定义#

void* watch_tv_task(void *arg)
{
// 线程函数必须返回void*类型,接受void*参数
// 这是pthread_create函数的要求
}

2. 参数传递过程#

// 主函数中
int channel = 10086;
pthread_create(&tv_thread, NULL, watch_tv_task, (void*)&channel);

这个过程可以形象地理解为:

  • 主函数是一个”快递员”
  • channel变量是”包裹”
  • &channel是”包裹地址”
  • (void*)是”标准化包装”
  • 线程函数是”收件人”

3. 参数接收与转换#

// 线程函数中
int *channel_number = (int*)arg;

这个过程就像是:

  • 收件人收到”标准化包装”的包裹
  • 需要”拆包”(类型转换)才能看到里面的内容
  • 最终得到原始的”包裹”(数据)

编译与运行#

编译命令#

在不同的Linux系统中,编译命令可能有所不同:

Ubuntu 22.04及更高版本:

Terminal window
gcc thread_example.c -o thread_example

较老版本的Linux系统:

Terminal window
gcc thread_example.c -o thread_example -pthread

运行结果#

主函数中变量的地址: 0x7ffee4b8a6ac
接收到的参数地址: 0x7ffee4b8a6ac
正在观看频道: 10086
主函数继续执行...

结果分析#

从输出结果可以看出:

  1. 主函数中的变量地址和线程函数接收到的地址是相同的
  2. 这证明了参数是通过地址传递的
  3. 线程成功访问并使用了传递的参数

注意事项与最佳实践#

1. 参数生命周期#

⚠️ 重要警告:传递给线程的参数必须在线程使用期间保持有效。

// ❌ 错误示例
void create_thread_wrong()
{
int local_var = 100;
pthread_create(&thread, NULL, thread_func, &local_var);
// local_var在这里就销毁了,线程可能访问无效内存
}
// ✅ 正确示例
void create_thread_correct()
{
int *param = malloc(sizeof(int));
*param = 100;
pthread_create(&thread, NULL, thread_func, param);
// 记得在线程函数中释放内存
}

2. 多参数传递#

当需要传递多个参数时,可以使用结构体:

typedef struct {
int id;
char name[50];
double value;
} ThreadParam;
void* thread_func(void *arg)
{
ThreadParam *param = (ThreadParam*)arg;
printf("ID: %d, Name: %s, Value: %.2f\n",
param->id, param->name, param->value);
return NULL;
}

3. 线程同步#

在实际应用中,应该使用pthread_join等待线程完成:

pthread_t thread;
pthread_create(&thread, NULL, thread_func, &param);
pthread_join(thread, NULL); // 等待线程完成

进阶应用示例#

示例:多线程计算数组和#

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#define ARRAY_SIZE 1000
#define THREAD_COUNT 4
typedef struct {
int *array;
int start;
int end;
long long sum;
} ThreadData;
void* calculate_sum(void *arg)
{
ThreadData *data = (ThreadData*)arg;
data->sum = 0;
for (int i = data->start; i < data->end; i++) {
data->sum += data->array[i];
}
printf("线程 %ld 计算了从 %d%d 的和: %lld\n",
pthread_self(), data->start, data->end - 1, data->sum);
return NULL;
}
int main()
{
int *numbers = malloc(ARRAY_SIZE * sizeof(int));
pthread_t threads[THREAD_COUNT];
ThreadData thread_data[THREAD_COUNT];
// 初始化数组
for (int i = 0; i < ARRAY_SIZE; i++) {
numbers[i] = i + 1;
}
// 创建线程计算不同部分的和
int chunk_size = ARRAY_SIZE / THREAD_COUNT;
for (int i = 0; i < THREAD_COUNT; i++) {
thread_data[i].array = numbers;
thread_data[i].start = i * chunk_size;
thread_data[i].end = (i == THREAD_COUNT - 1) ? ARRAY_SIZE : (i + 1) * chunk_size;
pthread_create(&threads[i], NULL, calculate_sum, &thread_data[i]);
}
// 等待所有线程完成
long long total_sum = 0;
for (int i = 0; i < THREAD_COUNT; i++) {
pthread_join(threads[i], NULL);
total_sum += thread_data[i].sum;
}
printf("总和: %lld\n", total_sum);
printf("理论值: %lld\n", (long long)ARRAY_SIZE * (ARRAY_SIZE + 1) / 2);
free(numbers);
return 0;
}

常见问题与解决方案#

问题1:段错误(Segmentation Fault)#

原因:线程访问了已经释放的内存 解决方案:确保参数的生命周期覆盖线程的整个执行过程

问题2:数据竞争(Race Condition)#

原因:多个线程同时访问和修改共享数据 解决方案:使用互斥锁(mutex)或其他同步机制

问题3:线程未按预期执行#

原因:主线程提前退出,导致子线程被强制终止 解决方案:使用pthread_join等待线程完成

总结#

本文详细介绍了C语言中线程的创建和参数传递机制,主要内容包括:

  1. 线程的基本概念:理解线程与进程的区别
  2. pthread_create函数:掌握线程创建的API
  3. 参数传递机制:理解地址传递和类型转换
  4. 实际应用示例:通过具体代码学习使用方法
  5. 注意事项:避免常见的编程陷阱
  6. 进阶应用:多线程协作的实例

掌握这些知识后,你就能够编写出高效、稳定的多线程程序。记住,多线程编程需要仔细考虑同步和资源管理问题,这是编写可靠程序的关键。

练习题#

  1. 编写一个程序,创建3个线程,每个线程打印不同的消息
  2. 实现一个多线程程序,计算数组中所有偶数的和
  3. 修改本文的示例,使用结构体传递多个参数给线程函数

参考资料#


希望这篇文章能帮助你理解C语言中的线程编程!如果有任何疑问,欢迎在评论区交流讨论。

C语言线程创建与参数传递详解教程
https://demo-firefly.netlify.app/posts/thread-creation-parameter-passing/
作者
长琴
发布于
2024-09-02
许可协议
CC BY-NC-SA 4.0
最后更新于 2024-09-02,距今已过 445 天

部分内容可能已过时

评论区

目录

Loading ... - Loading ...
封面
Loading ...
Loading ...
0:00 / 0:00