线程的基本概念
线程是进程中的一个单一的执行流。一个进程可以包含多个线程,这些线程共享进程中的资源,并且在相同的地址空间中执行。多线程是提高应用程序并行性的流行方法。例如,在浏览器中,不同的标签页可以视作独立的线程。
通俗解释
简单来说,一个程序(也就是进程)可以有多个小的执行单元,这些小的执行单元叫做线程。就像是一个工厂的流水线,每条流水线都能并行地完成工作,这样整个工厂(程序)就能更快地处理任务。
在现代应用中,多线程是非常有用的。例如在浏览器中,每一个标签页可能是一个独立的线程,这样如果一个标签页卡住了,其他标签页还能继续工作,而不会全部停顿。
示例代码:使用 Pthread 实现多线程
在 C 语言中,我们可以使用 POSIX 线程(Pthread)库来实现Linux系统下的多线程编程。POSIX 线程库是基于标准的 C/C++ 线程 API,包含在 <pthread.h>
头文件中。下面我们来写一个简单的多线程程序。
#include <pthread.h> // 包含 Pthread 库
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 标准库
// 线程要执行的函数
void* myThreadFunction(void* arg) {
int threadID = *((int*)arg);
printf("Hello from thread %d\n", threadID);
pthread_exit(NULL); // 线程结束
}
int main() {
pthread_t threads[3]; // 创建一个线程数组,包含3个线程
int threadArgs[3]; // 每个线程的参数
int rc; // 返回代码
for (int i = 0; i < 3; i++) {
threadArgs[i] = i + 1; // 给每个线程分配一个ID
printf("Creating thread %d\n", i + 1);
rc = pthread_create(&threads[i], NULL, myThreadFunction, (void*)&threadArgs[i]);
if (rc) {
printf("Error: unable to create thread %d\n", rc);
exit(-1);
}
}
// 等待所有线程完成
for (int i = 0; i < 3; i++) {
pthread_join(threads[i], NULL);
}
printf("All threads completed.\n");
return 0;
}
解释代码中的每个部分:
-
头文件包含:
#include <pthread.h>
:包含 POSIX 线程库的头文件,提供多线程的相关函数。#include <stdio.h>
和#include <stdlib.h>
用于输入输出和内存管理。
-
线程函数:
void* myThreadFunction(void* arg)
:这是每个线程的执行函数。它接受一个参数(这里是线程的 ID),并打印出线程的问候信息。pthread_exit(NULL)
:表示线程正常结束。
-
主函数:
pthread_t threads[3]
:声明一个包含 3 个线程的数组。threadArgs[i] = i + 1
:给每个线程分配一个唯一的 ID。pthread_create(...)
:创建线程,并启动myThreadFunction
函数执行。pthread_join(threads[i], NULL)
:等待所有线程完成,主程序会在所有线程执行完毕之后再继续。
如何编译和运行代码:
-
编译命令:
-
示例输出:
Creating thread 1
Creating thread 2
Creating thread 3
Hello from thread 1
Hello from thread 2
Hello from thread 3
All threads completed.
2. Pthread 的创建和终止
2.1 Pthread 的声明
在使用 Pthread 时,我们需要首先声明线程变量。Pthread 使用 pthread_t
类型来声明线程。
示例:
pthread_t threads[NUM_THREAD];
pthread_t
是一个结构类型,用于标识一个线程。在上面的代码中,threads
是一个包含 NUM_THREAD
个线程的数组。
2.2 Pthread 的创建
要创建一个新线程,我们可以使用 pthread_create()
函数:
int pthread_create(
pthread_t *thread, // 线程指针,用于接收新创建的线程标识
const pthread_attr_t *attr, // 线程属性参数,可以用于设置调度策略等
void *(*start_routine) (void *), // 线程函数,线程开始执行的地方
void *arg // 传递给线程函数的参数
);
-
参数解释:
thread
:指向pthread_t
对象的指针,用于存储新创建的线程标识。attr
:用于设置线程属性的参数,可以设置线程的调度策略、分离状态等。通常为NULL
表示使用默认属性。start_routine
:线程创建后将执行的函数,函数的类型为void* function(void*)
。arg
:指向传递给线程函数的参数的指针,可以为NULL
。
-
返回值:
- 成功时,
pthread_create()
返回0
。 - 如果失败,则返回错误号,且线程标识不确定。
- 成功时,
示例代码:创建线程
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 3
void* threadFunction(void* arg) {
int threadID = *((int*)arg);
printf("Hello from thread %d\n", threadID);
pthread_exit(NULL); // 线程完成工作后调用 pthread_exit 显式退出
}
int main() {
pthread_t threads[NUM_THREADS]; // 声明线程数组
int threadArgs[NUM_THREADS]; // 每个线程的参数
int rc; // 返回代码
for (int i = 0; i < NUM_THREADS; i++) {
threadArgs[i] = i + 1; // 给每个线程分配一个 ID
printf("Creating thread %d\n", i + 1);
rc = pthread_create(&threads[i], NULL, threadFunction, (void*)&threadArgs[i]);
if (rc) {
printf("Error: unable to create thread %d\n", rc);
exit(-1);
}
}
// 等待所有线程完成
for (int i = 0; i < NUM_THREADS; i++) {
pthread_join(threads[i], NULL);
}
printf("All threads completed.\n");
return 0;
}
在上面的代码中:
- 使用
pthread_create()
创建线程,并指定每个线程要执行的函数threadFunction
。 - 通过
pthread_join()
来等待每个线程的完成。
2.3 Pthread 的终止
线程在完成其任务时可以显式地调用 pthread_exit()
函数来终止:
void pthread_exit(void *value_ptr);
- 参数解释:
value_ptr
:指向一个整型变量的指针,用于存储线程的返回状态。这个变量应该是全局的,这样等待该线程的其他线程就可以读取它的返回状态。
线程退出时,通常会调用 pthread_exit()
,确保其他线程能够正确地获知该线程的退出状态。
在前面的示例代码中,我们使用了 pthread_exit(NULL)
,表示线程成功完成其任务且没有特定的返回值。
通俗解释
pthread_create()
就像给每个工人发放工作指令,告诉他们该做什么,以及如何做;而 pthread_exit()
就是让工人在完成任务后报告说“我已经完成了”。
在代码中,pthread_create()
创建了线程,并传递了一个函数 threadFunction
给它,这个函数是线程创建后将要执行的内容。当线程完成其工作后,调用 pthread_exit()
来退出。
2.4 示例 1:使用 pthread_exit()
函数
在这个示例中,我们创建了 5 个线程,每个线程执行一个简单的打印任务,输出“Hello World!”并带有线程的 ID。以下是完整代码:
#include <pthread.h> // 包含 Pthread 库
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 标准库
#define NUM_THREADS 5 // 定义线程数
// 线程函数
void *PrintHello(void *threadid) {
int tid = (int)threadid; // 将传入的参数转换为整数类型
printf("\n%d: Hello World!\n", tid);
pthread_exit(NULL); // 线程完成工作后退出
}
int main(int argc, char *argv[]) {
pthread_t threads[NUM_THREADS]; // 声明一个包含 NUM_THREADS 个线程的数组
int rc; // 返回代码
int t;
// 创建线程
for (t = 0; t < NUM_THREADS; t++) {
printf("Creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
if (rc) {
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
pthread_exit(NULL); // 主线程调用 pthread_exit 以确保子线程能够执行完毕
}
代码详细解释
-
头文件包含:
#include <pthread.h>
:包含 POSIX 线程库的头文件,提供多线程相关函数。#include <stdio.h>
和#include <stdlib.h>
用于输入输出和内存管理。
-
宏定义:
#define NUM_THREADS 5
:定义线程数量为 5。
-
线程函数
PrintHello
:void *PrintHello(void *threadid)
:每个线程执行的函数。- 函数参数
threadid
是线程的 ID,将它转换为int
类型以便输出。 - 使用
pthread_exit(NULL)
来退出线程,确保线程正常结束。
-
主函数
main
:- 声明
pthread_t threads[NUM_THREADS]
:包含 5 个线程的数组。 - 使用
pthread_create()
创建线程,循环NUM_THREADS
次,每次创建一个线程并传递线程 ID。 - 如果
pthread_create()
失败,打印错误代码并退出程序。 - 主线程调用
pthread_exit(NULL)
以防止主程序退出导致子线程未完成就被强制终止。
- 声明
输出结果示例
Creating thread 0
Creating thread 1
Creating thread 2
Creating thread 3
Creating thread 4
4: Hello World!
3: Hello World!
0: Hello World!
1: Hello World!
2: Hello World!
解释输出
在这个例子中,主线程首先创建 5 个子线程,然后每个线程执行 PrintHello
函数来打印 "Hello World!" 和线程的 ID。
需要注意的是,多线程的执行顺序并不确定。因此,输出的顺序可能会有所不同,例如线程 4 可能先输出,而线程 0 可能在最后输出。这是因为每个线程是独立并行执行的,具体的执行顺序取决于系统的线程调度。
通俗解释
这个程序的目的是演示如何使用 Pthread 来创建和管理多个线程。在代码中,我们创建了 5 个独立的线程,每个线程都执行相同的函数 PrintHello
,只是输出的内容不同——它们会显示各自的线程 ID。
在输出结果中,我们看到每个线程的输出顺序是随机的,这是因为多线程是并行执行的,系统会根据资源的可用性来调度每个线程何时运行。这种随机性是多线程编程的特点,也是它的优势所在——可以同时做多个任务,从而提高效率。
pthread_exit()
的作用
pthread_exit(NULL)
用于让线程正常结束。它在主线程中调用,确保所有的子线程在主线程结束之前都能够顺利完成任务。否则,如果主线程执行完毕,整个程序可能会直接退出,而导致子线程的执行被强制中断。
总结
- 使用
pthread_create()
来创建线程,并传递函数指针作为线程的任务。 pthread_exit()
用于显式退出线程,确保它们有机会完成其任务。- 多线程的输出顺序可能是随机的,因为线程是并行执行的。
2.5 示例 2:pthread_exit()
的影响
在多线程程序中,如果主线程(即 main()
函数)在子线程完成之前退出,子线程会自动终止。因此,推荐在 main()
中使用 pthread_exit()
,以确保所有线程都有机会完成其任务。
#include <pthread.h> // 包含 Pthread 库
#include <stdio.h> // 标准输入输出库
#include <stdlib.h> // 标准库
#include <unistd.h> // 包含 sleep() 函数
// 线程函数
void *PrintHello(void *threadid) {
sleep(2); // 线程休眠2秒
printf("Hello World!\n");
pthread_exit(NULL); // 线程正常退出
}
int main(int argc, char *argv[]) {
pthread_t thread; // 声明一个线程
int rc; // 返回代码
void *i;
printf("In main: create thread\n");
rc = pthread_create(&thread, NULL, PrintHello, i);
if (rc) {
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(1);
}
printf("Main thread exits!\n");
// 如果取消注释 pthread_exit(NULL),主线程会等待所有线程完成后再退出
// pthread_exit(NULL);
return 0; // 主线程直接退出
}
代码解释
-
头文件包含:
#include <pthread.h>
:包含 Pthread 库的头文件,用于多线程操作。#include <stdio.h>
和#include <stdlib.h>
:标准输入输出和内存管理库。#include <unistd.h>
:包含sleep()
函数,用于让线程休眠指定时间。
-
线程函数
PrintHello
:void *PrintHello(void *threadid)
:线程的任务是休眠 2 秒后打印 "Hello World!"。sleep(2)
:让线程休眠 2 秒。pthread_exit(NULL)
:线程完成任务后调用pthread_exit()
进行正常退出。
-
主函数
main
:- 声明了一个线程
pthread_t thread
。 - 使用
pthread_create()
创建线程,线程将执行PrintHello
函数。 pthread_exit(NULL)
被注释掉了。如果取消注释,则主线程会等待所有子线程完成工作后再退出。
- 声明了一个线程
输出结果
-
如果
pthread_exit(NULL)
被注释掉(如代码所示):In main: create thread Main thread exits!
-
主线程在子线程还没有完成任务时就退出了,因此子线程会被强制终止,导致 "Hello World!" 没有被输出。
-
如果
pthread_exit(NULL)
没有被注释掉In main: create thread Main thread exits! Hello World!
主线程调用了
pthread_exit()
,使得主线程会等待子线程完成所有任务后再退出。这样 "Hello World!" 会正常输出。
通俗解释
在这个例子中,我们创建了一个子线程,子线程的任务是等待 2 秒后打印 "Hello World!"。但是,如果主线程(main()
)在子线程完成之前退出,那么所有的子线程都会被强制终止。因此,我们建议在 main()
中使用 pthread_exit()
,这样主线程不会立即结束,而是等待所有的子线程完成工作。
可以把主线程想象成一场演出中的主持人,而子线程是表演者。如果主持人(主线程)在表演还没结束的时候就离开了,那么表演者(子线程)就不得不中断演出。使用 pthread_exit()
可以确保主持人等到所有表演者完成他们的演出后再离场。
pthread_exit()
的作用
- 当
pthread_exit()
被调用时,主线程不会立即退出,它会等待所有的子线程完成。 - 如果主线程直接调用
return
,程序会结束,所有未完成的线程会被强制终止,导致一些任务可能无法完成。
总结
pthread_create()
:用于创建线程,主线程可以用它来启动子线程。pthread_exit()
:用于确保主线程等待所有子线程完成任务后再退出。- 主线程的退出方式会影响子线程:如果主线程直接退出,子线程将被强制终止;使用
pthread_exit()
则可避免这个问题