1. 代码目的
我们希望创建一个程序:
- 启动多个线程,每个线程计算一个数字的平方值。
- 每个线程将计算结果返回给主线程。
- 主线程接收每个线程的返回值,并将结果打印出来。
在这个例子中,我们通过传递不同的参数给每个线程,来让每个线程计算不同数字的平方值。
2. 代码实现
以下是代码的完整实现:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUMBER_OF_PROCESS 5 // 定义线程数量
// 线程函数,用于计算输入数字的平方
void* square(void* param) {
int a = *(int*) param; // 将参数转换为int指针并解引用
int* result = (int*) malloc(sizeof(int)); // 动态分配内存存储返回值
*result = a * a; // 计算平方
return result; // 返回结果
}
int main(int argc, char const *argv[]) {
int number = NUMBER_OF_PROCESS;
pthread_t t1_id[number]; // 用于存储线程ID
int* result; // 用于存储线程返回值
for (int i = 0; i < number; i++) {
// 创建线程,传递i作为参数
pthread_create(&t1_id[i], NULL, square, &i);
pthread_join(t1_id[i], (void**) &result); // 等待线程完成并获取返回值
printf("The %d thread input number is %d, result is %d\n", i, i, *result);
free(result); // 释放动态分配的内存
}
return 0;
}
3. 代码逐行解析
全局定义
#define NUMBER_OF_PROCESS 5
宏定义NUMBER_OF_PROCESS
指定了要创建的线程数量,在这里我们设置为5。这样方便在代码中灵活更改线程数量。
线程函数 square
void* square(void* param) {
int a = *(int*) param; // 将参数转换为int指针并解引用
int* result = (int*) malloc(sizeof(int)); // 动态分配内存存储返回值
*result = a * a; // 计算平方
return result; // 返回结果
}
- 参数处理:
param
是一个void*
指针,传递的是整数的地址。将其转换为int*
指针后,使用*param
来获取传入的整数值。 - 内存分配:使用
malloc
在堆上分配一个int
大小的内存,用于存储计算结果。这样当线程返回时,主线程可以通过指针访问该结果。 - 返回值:返回指向结果的指针(
result
),可以在主线程中通过pthread_join
获取。
主函数 main
int main(int argc, char const *argv[]) {
int number = NUMBER_OF_PROCESS;
pthread_t t1_id[number];
int* result;
for (int i = 0; i < number; i++) {
pthread_create(&t1_id[i], NULL, square, &i); // 创建线程并传递参数
pthread_join(t1_id[i], (void**) &result); // 等待线程完成并获取返回值
printf("The %d thread input number is %d, result is %d\n", i, i, *result);
free(result); // 释放动态分配的内存
}
return 0;
}
- 线程创建:在
for
循环中,通过pthread_create
创建线程,并传入线程函数square
。每次循环中,将i
的地址传递给square
函数。注意,这里传递的是变量i
的地址。 - 线程同步:使用
pthread_join
等待每个线程完成,并获取其返回的结果。pthread_join
的第二个参数是一个void**
指针,通过强制类型转换,我们可以将其转为int*
类型并存储到result
中。 - 打印和释放内存:打印线程返回的平方结果后,使用
free(result);
释放动态分配的内存,防止内存泄漏。
4. 存在的问题:数据竞争
当前代码在传递参数&i
时会产生数据竞争,因为所有线程共享变量i
。当pthread_create
启动线程时,如果在线程访问变量i
之前主线程的i
值已经更新,那么线程读取的值可能不正确,导致错误结果。
5. 解决方法:使用单独的参数数组
通过定义一个参数数组,将每个线程的参数存储在独立的位置,可以避免数据竞争问题。以下是改进后的代码:
int main(int argc, char const *argv[]) {
int number = NUMBER_OF_PROCESS;
pthread_t t1_id[number];
int args[number]; // 参数数组,每个线程一个独立参数
int* result;
for (int i = 0; i < number; i++) {
args[i] = i; // 将每个线程的参数存储在独立的数组位置中
pthread_create(&t1_id[i], NULL, square, &args[i]);
pthread_join(t1_id[i], (void**) &result);
printf("The %d thread input number is %d, result is %d\n", i, i, *result);
free(result);
}
return 0;
}
6. 运行结果示例
假设线程的输入值分别是0
、1
、2
、3
和4
,输出结果应该如下:
The 0 thread input number is 0, result is 0
The 1 thread input number is 1, result is 1
The 2 thread input number is 2, result is 4
The 3 thread input number is 3, result is 9
The 4 thread input number is 4, result is 16
7. 关键知识点总结
- 线程函数参数传递:使用
void*
来传递不同类型的参数,传递后在函数内部将其转换回正确的类型。 - 动态内存分配:在线程函数中,使用
malloc
分配内存以返回计算结果。主线程在使用完返回值后,需要调用free
释放内存。 - 数据竞争与解决方案:避免数据竞争的一个常用方法是为每个线程创建独立的参数,而不是共享同一个变量。
总结
本文通过一个简单的线程计算平方值的例子,介绍了pthread
库的基础用法。我们学习了如何传递参数、获取线程返回值,并讨论了内存管理和数据竞争的问题。希望这个示例可以帮助你理解多线程编程的基础概念和实现方法。