1. 线程创建:pthread_create()
pthread_create()
是 POSIX 线程库(pthread)中用于创建新线程的函数。调用该函数后系统就会启动一个与主线程并发的线程,并使其跳转到入口函数处执行。
#include <pthread.h>
int pthread_create(
pthread_t *thread, // 指向线程标识符的指针
const pthread_attr_t *attr, // 线程属性(通常设为 NULL 使用默认值)
void *(*start_routine)(void*), // 线程执行的函数
void *arg // 传递给线程函数的参数
);
参数说明:
- pthread_t* thread:输出型参数,存储新创建线程的标识符(线程 ID)。
- const pthread_attr_t* attr:设置线程的属性(如栈大小、调度策略等)。 常用值:NULL(使用默认属性)。
- void* (*)(void*) start_routine:新线程启动后执行的函数(线程入口点)。 要求:必须返回 void*,且接受一个 void* 参数。
- void* arg:传递给 start_routine 的参数。 注意:若无需参数,可传 NULL;若需传递多个参数,可封装为结构体。
返回值:
- 成功:返回 0。
- 失败:返回错误码(如 EAGAIN、EINVAL 等),但不设置 errno。
示例代码:
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void* start_routine(void* arg)
{
cout << "start_routine: 线程创建成功" << endl;
return nullptr;
}
int main()
{
pthread_t tid;
int n = pthread_create(&tid, nullptr, start_routine, nullptr);
if(n != 0)
{
cout << "线程创建失败: " << strerror(n) << endl;
}
while(true);
return 0;
}
2. 线程等待:pthread_join()
pthread_join() 是 POSIX 线程库中用于等待线程结束并回收其资源的函数。
主线程如何取得线程运行结束的返回值呢?我们可以使用pthread_join()函数来对指定线程进行等待,并获取其返回值。和waitpid()函数一样,调用这个函数会使主线程阻塞在调用处直到被等待的指定线程运行结束。
和多进程编程一样,线程如果不进行等待回收,那么其就会一直保留其运行结果等信息,造成内存泄漏。除此之外,与多进程编程不一样的是,一个进程的多个线程共享主线程的地址空间,一旦主线程退出,其创建的所有线程都会被强制终止,无论其是否执行完。
所以在上面的例子当中,线程启动之后我们让主线程陷入了死循环当中,避免其提前退出。
#include <pthread.h>
int pthread_join(
pthread_t thread, // 要等待的线程 ID
void **retval // 指向线程返回值的指针(可选)
);
核心功能:
- 阻塞等待:调用 pthread_join() 的线程会暂停执行,直到目标线程终止。
- 资源回收:线程终止后,其占用的系统资源(如线程描述符、栈空间)会被释放。 若不调用 pthread_join(),终止的线程会成为 “僵尸线程”,造成资源泄漏。
- 获取返回值:通过 retval 参数获取目标线程的返回值(start_routine 的返回值或 pthread_exit() 的参数)。
参数说明:
- pthread_t thread:指定要等待的线程 ID(由 pthread_create() 返回)。
- void** retval:输出型参数,存储线程的返回值(即线程函数 start_routine 的返回值)。 若无需获取返回值,可传 NULL。
返回值:
- 成功:返回 0。
- 失败:返回错误码(如 EDEADLK、ESRCH 等)。
示例代码:
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void* start_routine(void* arg)
{
cout << "start_routine: 线程创建成功" << endl;
for(int i = 0; i < 5; i++)
{
cout << "计数----->" << i << endl;
sleep(1);
}
return (void*)10;
}
int main()
{
pthread_t tid;
int n = pthread_create(&tid, nullptr, start_routine, nullptr);
if(n != 0)
{
cout << "线程创建失败: " << strerror(n) << endl;
}
int result;
int m = pthread_join(tid, (void**)&result);
if(m != 0)
{
cout << "线程等待失败: " << strerror(m) << endl;
}
cout << "线程的返回值为: " << result << endl;
return 0;
}
3. 线程终止
除主线程以外,线程的正常终止有三种情况:
- 从入口函数的return处返回
- 调用pthread_exit()函数退出(调用exit()函数会导致整个进程退出)
- 某个线程调用pthread_cancel()来终止指定线程
3.1 pthread_exit()
#include <pthread.h>
void pthread_exit(void *retval); // 无返回值,终止当前线程
核心功能:
- 终止线程执行:调用 pthread_exit() 的线程会立即停止执行,并释放其占用的资源(如栈空间),但不会释放整个进程的资源。
- 传递返回值:retval 作为返回值被传递给等待该线程的其他线程。
- 不影响其他线程:仅终止当前线程,不会影响进程中的其他线程或主线程。
参数说明:
- void* retval:线程的返回值,可通过 pthread_join() 的 retval 参数获取。 若无需返回值,可传 NULL。
3.2 pthread_self()
pthread_self()函数用于线程获取自身的线程ID。
#include <pthread.h>
pthread_t pthread_self(void); // 返回当前线程的 ID
返回调用该函数的线程的唯一标识符。
3.3 pthread_cancel()
pthread_cancel() 是 POSIX 线程库中用于请求终止某一个线程的函数。
#include <pthread.h>
int pthread_cancel(pthread_t thread); // 请求取消指定线程
核心功能:
- 发送取消请求:pthread_cancel() 向目标线程发送一个 “取消请求”,而非强制终止。线程是否响应以及如何响应取决于其取消状态和取消类型。
- 取消点:预定义的系统调用(如 sleep()、read()、write()、pthread_join() 等),线程在执行这些函数时会检查并处理取消请求。
参数说明:
- pthread_t thread:要取消的线程 ID(由 pthread_create() 返回)。
返回值:
- 成功:返回 0。
- 失败:返回错误码(如 ESRCH,表示线程 ID 不存在)。
示例代码:
// 终止自己==pthread_exit()
pthread_cancel(pthread_self());
4. 线程分离:pthread_detach()
pthread_detach() 是 POSIX 线程库中用于将线程设置为分离状态的函数。
分离状态的线程在终止后,会自动释放其占用的系统资源(如线程描述符、栈空间),无需其他线程调用 pthread_join() 回收。
并且其他线程调用pthread_join()对分离状态的线程进行回收是非法的,会导致未定义错误。
#include <pthread.h>
int pthread_detach(pthread_t thread); // 设置线程为分离状态
参数说明:
- pthread_t thread:要设置为分离状态的线程 ID(由 pthread_create() 返回)。
返回值:
- 成功:返回 0。
- 失败:返回错误码(如 ESRCH、EINVAL 等)。
注意事项:
- 分离状态不可逆转:一旦线程被设置为分离状态,无法再变回 joinable 状态。
- 返回值无法获取:分离线程的返回值会被自动丢弃,不能通过 pthread_join() 获取。
- 资源释放的确定性:分离线程终止后,系统会立即回收其资源,无需等待其他线程操作。
- 错误处理:若对已终止的线程调用 pthread_detach(),可能返回 ESRCH。 若对已分离的线程重复调用 pthread_detach(),可能返回 EINVAL。
示例代码:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* detached_thread(void* arg) {
printf("分离线程开始运行...\n");
sleep(2); // 模拟耗时操作
printf("分离线程结束\n");
return NULL;
}
int main() {
pthread_t thread_id;
// 创建新线程
if (pthread_create(&thread_id, NULL, detached_thread, NULL) != 0) {
perror("线程创建失败");
return 1;
}
// 将线程设置为分离状态
if (pthread_detach(thread_id) != 0) {
perror("设置分离状态失败");
return 1;
}
printf("主线程继续执行,不等待分离线程\n");
// 主线程可以提前退出,分离线程仍会继续执行
sleep(1);
printf("主线程退出\n");
return 0;
}
// 分离自己
pthread_detach(pthread_self());
5. 线程标识符
在 Linux 系统中,pthread_t 类型的线程标识符(tid)本质上是一个轻量级进程(LWP)ID或指向线程控制块的指针。
5.1 线程控制块(TCB)
每个线程在内核中对应一个 task_struct 结构(与进程相同),但共享父进程的资源。
而在用户空间中,经过pthread库的包装之后另外设置了一种数据结构来维护额外的线程数据,即TCB,包括如下控制信息:
- 线程状态(运行、阻塞等)
- 线程栈地址和大小
- 信号掩码
- 内核 LWP ID
5.2 pthread_t类型的本质
在 Linux 中,pthread_t 的具体类型定义取决于实现:
- glibc/NPTL:pthread_t 通常是一个 struct pthread*,即指向线程控制块的指针。
- 用户可见性:pthread_t 对用户是不透明的,只能通过 POSIX 线程 API 操作。