2693 字
13 分钟
线程(一)线程的基本使用
2025-07-29
无标签

线程基本介绍#

主要作用 多线程的主要作用:引入多核能力,让程序能够利用多核处理器的并行计算能力,真正实现同时执行多个任务。线程是最小的调度单位。

多线程不依赖于硬件配置

  • 即使在单处理器或单核系统上运行,多线程依然有以下好处:
    • 改善响应时间;
    • 提高吞吐量(某些线程阻塞时,其他线程仍可运行)。
  • 多线程是一种软件层面的设计模型,与处理器数量无关。

线程私有数据#

每个线程包含如下执行环境所需的信息:

信息项描述
线程 ID唯一标识线程
寄存器值存储当前执行状态
用于函数调用和局部变量存储
调度优先级和策略影响线程被调度的方式
信号屏蔽字控制线程对信号的响应
errno 变量错误码(线程私有)
线程私有数据每个线程可拥有独立的数据副本

注意:除了上述线程私有数据外,所有线程共享进程的全局内存、堆、代码段、文件描述符等资源

与进程比较#

进程原语线程原语描述
forkpthread_create创建新的控制流
exitpthread_exit从现有的控制流中退出
waitpidpthread_join从控制流中得到退出状态
atexitpthread_cancel_push注册在退出控制流时调用的函数
getpidpthread_self获取控制流的 ID
abortpthread_cancel请求控制流的非正常退出

线程标识#

就像每个进程有一个进程ID一样,每个线程也有一个线程ID。进程 ID在整个系统中是唯一的,但线程ID不同,线程ID只有在它所属的进程上下文中才有意义。
进程ID,它是用pid_t数据类型来表示的,是一个非负整数。线程ID是用pthread_t数据类型来表示的,实现的时候可以用一个结构来代表pthread_t数据类型,所以可移植的操作系统实现不能把它作为整数处理。因此必须使用一个函数来对两个线程ID进行比较。

函数 pthread_equal#

int pthread_equal(pthread_t t1, pthread_t t2);

作用

pthread_equal 用于比较两个线程标识符(thread IDs)是否相等。由于线程 ID 应该被视为不透明类型,因此不能直接通过 == 运算符来比较两个 pthread_t 类型的值,而应使用 pthread_equal 函数进行比较。

参数

  • pthread_t t1:要比较的第一个线程 ID。
  • pthread_t t2:要比较的第二个线程 ID。

返回值

  • 如果两个线程 ID 相等,则返回一个非零值。
  • 如果两个线程 ID 不相等,则返回 0。

错误

此函数总是执行成功,不会失败。

函数 pthread_self#

pthread_t pthread_self(void);

作用
pthread_self() 用于获取调用线程的线程 ID。这个 ID 与创建该线程时在 pthread_create 调用中返回给 *thread 的值相同。

返回值
该函数总是成功,返回调用线程的 ID。


线程创建#

pthread_create#

可以使用pthread_create创建线程,默认情况下,新线程是可结合(joinable)的。可通过 pthread_attr_setdetachstate 将线程设置为分离(detached)状态。

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

作用
pthread_create() 函数用于在调用进程中创建一个新的线程。新线程通过调用 start_routine 函数开始执行,参数 arg 会作为 start_routine 的唯一参数传入。

参数

  • pthread_t *restrict thread:指向用于存储新线程 ID 的缓冲区指针。
  • const pthread_attr_t *restrict attr:指向用于设置新线程属性的结构体指针,若为 NULL 则使用默认属性。
  • void *(*start_routine)(void *):新线程开始执行的函数地址。
  • void *restrict arg:传递给 start_routine 的参数。

返回值

  • 成功时返回 0;
  • 失败时返回错误码,此时 *thread 的内容是未定义的。

错误码

  • EAGAIN:资源不足,无法创建新线程;或系统线程数达到限制(如 RLIMIT_NPROC/proc/sys/kernel/threads-max/proc/sys/kernel/pid_max)。
  • EINVALattr 中包含无效设置。
  • EPERM:没有权限设置 attr 中指定的调度策略和参数。

线程终止#

终止方式#

新线程可以通过以下几种方式终止:

  • 调用 pthread_exit 并指定退出状态,其他线程可以通过 pthread_join 获取该状态。
  • start_routine 返回,等同于调用 pthread_exit 并使用返回值作为退出状态。
  • 被取消(参见 pthread_cancel)。
  • 进程中的任意线程调用了 exit 或主线程从 main() 返回,这会导致进程中所有线程终止。

pthread_exit#

[[noreturn]] void pthread_exit(void *retval);

作用
pthread_exit() 函数用于终止调用它的线程,并可通过 retval 参数返回一个值。如果该线程是可连接的(joinable),则这个返回值可以被同一进程中的其他线程通过调用 pthread_join 获取。
在线程终止时,所有尚未弹出的清理处理程序(由 pthread_cleanup_push 建立)会按照与压入相反的顺序被弹出并执行。如果线程有线程特定数据(thread-specific data),在清理处理程序执行完毕后,相应的析构函数会被调用(调用顺序未指定)。
线程终止时,进程共享资源(如互斥锁、条件变量、信号量和文件描述符)不会被释放,也不会调用通过 atexit 注册的函数。 当一个进程中的最后一个线程终止时,整个进程会像调用了 exit 并传入退出状态码 0 一样终止,此时进程共享资源会被释放,且 atexit 注册的函数也会被调用。

参数

  • void *retval:指向线程退出时返回的值。这个值可以被其他线程通过 pthread_join() 获取。注意:该指针指向的内容不应位于调用线程的栈上,因为线程终止后其栈内容是未定义的。

返回值

  • 此函数不会返回到调用者(标记为 [[noreturn]])。

错误

  • 该函数总是成功,不会返回错误码。

注意事项

  • 除主线程外,任何线程的起始函数中执行 return 操作,都会隐式调用 pthread_exit(),并将函数的返回值作为线程的退出状态。
  • 为了让其他线程能够继续执行,主线程应调用 pthread_exit() 而不是 exit 来终止自身。
  • retval 指向的数据不应位于调用线程的栈上,因为线程终止后栈的内容是未定义的。

栈的清理#

与进程中的atexit类似,线程可以安排退出时需要调用的函数,这样的函数称为线程清理处理程序(thread cleanup handler)。一个线程可以建立多个清理处理程序。处理程序记录在栈中,也就是说,它们的执行顺序与它们注册时相反。

void pthread_cleanup_push(void (*routine)(void *), void *arg);
void pthread_cleanup_pop(int execute);

作用
pthread_cleanup_pushpthread_cleanup_pop 用于操作调用线程的线程取消清理处理程序栈。清理处理程序是一个在线程被取消(或在下面描述的各种其他情况下)时自动执行的函数;例如,它可以解锁互斥锁,以便进程中的其他线程可以使用该锁。

pthread_cleanup_push() 函数将 routine 推入清理处理程序栈的顶部。当 routine 稍后被调用时,它将接收 arg 作为参数。

pthread_cleanup_pop() 函数从清理处理程序栈顶移除routine,并在 execute 非零时选择性地执行它。

参数

  • void (*routine)(void *):指向清理处理程序函数的指针。该函数将在清理时被调用。
  • void *arg:传递给清理处理程序函数的参数。
  • int execute:非零值表示在弹出时执行清理处理程序;零值表示仅从栈中移除处理程序但不执行。

返回值
这些函数不返回值。

函数 pthread_cancel#

#include <pthread.h>

int pthread_cancel(pthread_t thread);

作用
pthread_cancel() 函数用于向指定的目标线程发送一个取消请求。目标线程是否以及何时响应这个取消请求,取决于该线程自身的两个属性:取消状态(cancelability state)取消类型(cancelability type)

  1. 取消状态:通过 pthread_setcancelstate 设置。

    • 启用 (PTHREAD_CANCEL_ENABLE):默认状态。接收到取消请求后,会根据取消类型来决定何时执行取消操作。
    • 禁用 (PTHREAD_CANCEL_DISABLE):如果线程禁用了取消功能,那么取消请求会一直保持在队列中,直到该线程重新启用取消功能。
  2. 取消类型:在取消状态为“启用”时生效,通过 pthread_setcanceltype 设置。

    • 延迟 (PTHREAD_CANCEL_DEFERRED):默认类型。取消请求会被推迟,直到线程下次执行到一个取消点(cancelation point)函数时才被处理。常见的取消点函数包括 sleep(), read(), write(), pthread_join() 等。
    • 异步 (PTHREAD_CANCEL_ASYNCHRONOUS):线程可以在任何时候被立即取消,而无需等待到达一个取消点。

当线程最终响应取消请求时,它会执行一系列清理操作(如调用 pthread_cleanup_push 注册的清理函数)后终止。

参数

  • pthread_t thread:要取消的目标线程的ID。

返回值

  • 成功时返回 0。注意,这仅表示取消请求已成功发送或排队,并不意味着目标线程已经终止。
  • 失败时返回一个非零的错误码
    • ESRCH:找不到具有指定 thread ID 的线程。

要确认一个线程是否真的因为取消而终止,需要使用 pthread_join() 来等待该线程,并检查其退出状态是否为 PTHREAD_CANCELED


线程属性#

pthread_attr_t


程序示例#

#include "pthread.h"
#include "stdio.h"

void *task_handle(void *arg) {
  int *num = arg;
  printf("%d\n", *num);
  *num += 1;
  return num;
}

int main() {
  pthread_t thread;
  int handle_arg = 2233;
  pthread_create(&thread, NULL, task_handle, (void *)&handle_arg);
  int *rtn;
  pthread_join(thread, (void **)&rtn);
  printf("线程执行完毕,返回%d\n", *rtn);
  return 0;
}
  pthread ./thread_test
2233
线程执行完毕,返回2234
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void cleanup_handler(void *arg) {
    printf("Cleanup handler called: %s\n", (char *)arg);
}

void *thread_func(void *arg) {
    printf("Thread started. Thread ID: %lu\n", pthread_self());

    // 注册清理函数
    pthread_cleanup_push(cleanup_handler, "First cleanup");
    pthread_cleanup_push(cleanup_handler, "Second cleanup");

    // 让线程休眠一会儿,等待被取消
    for (int i = 0; i < 5; ++i) {
        printf("Thread running... %d\n", i);
        sleep(1);
    }

    // 弹出清理函数(0 表示不执行,1 表示执行)
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);

    printf("Thread finished normally.\n");
    pthread_exit((void *)0);
}

int main() {
    pthread_t tid;
    int ret;

    // 创建线程
    ret = pthread_create(&tid, NULL, thread_func, NULL);
    if (ret != 0) {
        perror("pthread_create");
        exit(1);
    }

    // 主线程与新线程比较
    if (pthread_equal(pthread_self(), tid)) {
        printf("Main thread and new thread are the same.\n");
    } else {
        printf("Main thread and new thread are different.\n");
    }

    // 等待2秒后取消线程
    sleep(2);
    printf("Main thread cancels the new thread.\n");
    pthread_cancel(tid);

    void *res;
    pthread_join(tid, &res);

    if (res == PTHREAD_CANCELED) {
        printf("Thread was canceled.\n");
    } else {
        printf("Thread exited normally.\n");
    }

    return 0;
}

输出结果:

➜  pthread ./thread_test
Main thread and new thread are different.
Thread started. Thread ID: 140113254692544
Thread running... 0
Thread running... 1
Main thread cancels the new thread.
Thread running... 2
Cleanup handler called: Second cleanup
Cleanup handler called: First cleanup
Thread was canceled.
线程(一)线程的基本使用
https://milkfunc.top/posts/线程一线程的基本使用/
作者
CapCake
发布于
2025-07-29
许可协议
CC BY-NC-SA 4.0