C 语言 pthread_self()

2025年1月7日 | 阅读10分钟

pthread_self() 是 C 编程语言中的一个方法,可以在名为 pthreads (POSIX 线程) 的库中找到。此函数用于获取调用线程的唯一 标识符 - 线程 ID (TID)。这些 TID 非常有用,因为不同的多线程应用程序可以使用这些 TID 来识别自己。

引言

  • 多线程:在这种编程技术中,一个进程内可以启用多个线程独立工作,同时使用相同的资源,例如内存空间。
  • POSIX 线程 (pthreads):这些标准线程库定义了一组 C 编程语言类型、函数和常量。
  • 线程 ID (TID):在多线程程序中,每个线程都由一个唯一的线程 ID 标识。TID 是系统分配的标识符,用于区分同一进程内的不同线程。

历史

POSIX 线程起源于 20 世纪 80 年代,作为 POSIX 工作的一部分,旨在标准化 UNIX 类操作系统上的线程接口。pthreads API 被设计为与不同的 UNIX 平台完全兼容,以便程序员可以编写可在多个操作系统平台上运行的可移植多线程应用程序。

pthreads 库中的另一个函数是 pthread_self(),它帮助线程轻松获取自己的线程 ID。

POSIX 线程的主要特性

C 语言中的 POSIX 线程有几个特性。POSIX 线程的一些主要特性如下:

1. 线程创建

该库提供用于创建线程的函数。线程创建的主要函数是 pthread_create。它允许开发人员在程序中创建一个新线程,并指定该线程将执行的函数。

2. 线程终止

线程可以使用 pthread_exit 终止。此函数允许线程退出,并将一个值返回给调用线程。

3. 线程同步

POSIX 线程提供了各种用于同步线程的机制,包括互斥量 (pthread_mutex_t)、条件变量 (pthread_cond_t) 和信号量 (sem_t)。这些同步原语有助于协调多个线程的执行并防止竞态条件。

4. 线程连接

pthread_join 函数用于等待线程完成执行。它允许一个线程等待另一个线程的终止。

5. 线程属性

创建线程时可以指定线程属性。它包括与调度策略、栈大小和其他特性相关的属性。

6. 线程安全函数

POSIX 线程库包含许多标准 C 库函数的线程安全版本。这些线程安全函数确保可以安全地从多个线程调用它们而不会相互干扰。

示例

让我们举一个例子来说明 C 语言中的 pthread_self() 函数。

输出

Inside the thread. Thread ID: 139836372760128
Back in the main thread. Main Thread ID: 139836372764480

说明

1. 包含头文件

  • #include <stdio.h>: 它包含用于 printf 等函数的标准输入/输出库。
  • #include <pthread.h>: 它包含用于多线程功能的 POSIX 线程库。

2. 线程函数 (thread_function)

  • 此函数由新创建的线程执行。
  • pthread_t tid = pthread_self();: 它使用 pthread_self() 检索当前线程的线程 ID 并将其存储在变量 tid 中。
  • printf("Inside the thread. Thread ID: %lu\n", tid);: 它将线程 ID 打印到控制台。

3. 主函数

  • pthread_t thread;: 它声明一个 pthread_t 类型的变量 thread,用于存储新创建线程的线程 ID。
  • pthread_create(&thread, NULL, thread_function, NULL): 它使用 pthread_create 创建一个新线程。新线程将执行 thread_function。线程 ID 存储在变量 thread 中。
  • pthread_join(thread, NULL): 它等待新创建的线程完成执行。它确保主线程在新线程完成之前不会继续执行。
  • pthread_t main_tid = pthread_self();: 它使用 pthread_self() 检索主线程的线程 ID 并将其存储在变量 main_tid 中。
  • printf("Back in the main thread. Main Thread ID: %lu\n", main_tid);: 它将主线程的线程 ID 打印到控制台。
  • return 0;: 它表示程序执行成功。

4. 编译

  • 程序应使用适当的线程库标志进行编译,例如 GCC 的 -lpthread,以链接 pthread 库。

5. Execution (执行)

  • 当程序执行时,它将创建一个新线程,打印其线程 ID,等待其完成,然后打印主线程的线程 ID。

时间和空间复杂度

时间复杂度

  • thread_function 和主函数内部的操作主导了时间复杂度。
  • 主要操作包括创建线程 ('pthread_create') 和等待线程完成 ('pthread_join')。
  • 这两个操作通常具有常数时间复杂度 O(1),因为两个操作的时间都不会随输入的大小而增加。
  • 因此,所提供代码的总体时间复杂度为 O(1)

空间复杂度

  • 主要的内存使用与线程创建和线程 ID 的存储有关。
  • 'pthread_t' 变量 ('thread''main_tid') 和 'int' 变量 ('thread_ids') 用于存储线程 ID。
  • 这些变量所需的空间是恒定的,不依赖于输入的大小。
  • 因此,所提供代码的总体空间复杂度也为 O(1)

示例 2

让我们再举一个例子来说明 C 语言中的 pthread_self() 函数。

输出

Thread 0: Hello from the thread!
Thread 1: Hello from the thread!
Thread 2: Hello from the thread!
Back in the main thread.

说明

1. 线程函数 (thread_function)

  • thread_function 是每个线程将执行的函数。它接受一个参数 void *arg,该参数预计是指向表示线程唯一 ID 的整数的指针。
  • 在函数内部,参数被转换为整数 (int thread_id = *(int *)arg;),表示线程的唯一标识符。
  • 线程向控制台打印一条消息,包括其唯一 ID,表明它已启动。
  • 之后,线程返回 (return NULL;),表示其任务已完成。

2. Main 函数

  • #define NUM_THREADS 3: 一个预处理器指令,将线程数定义为 3。
  • pthread_t threads[NUM_THREADS];: 一个数组,用于存储已创建线程的线程 ID。
  • int thread_ids[NUM_THREADS];: 一个数组,用于存储每个线程的唯一 ID。

3. 创建线程

  • 使用循环 (for (int i = 0; i < NUM_THREADS; ++i)) 创建多个线程。
  • thread_ids[i] = i;: 它为每个线程分配一个唯一的 ID。
  • pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]): 它使用 pthread_create 创建一个新线程。线程将执行 thread_function,并将唯一 ID 作为参数传递。
  • 如果在线程创建过程中发生错误,则会将错误消息打印到标准错误流 (stderr),并且程序将以错误代码退出。

4. 等待线程完成

  • 另一个循环用于等待所有线程完成。
  • pthread_join(threads[i], NULL): 它等待 ID 为 threads[i] 的线程完成执行。如果在线程连接过程中发生错误,则会将错误消息打印到 stderr,并且程序将以错误代码退出。

5. 主线程

  • 所有线程完成后,主线程打印一条消息,表明它已返回主线程。

6. 编译和执行

  • 代码需要使用适当的线程库标志进行编译,例如 GCC-lpthread
  • gcc -o multithread_example multithread_example.c -lpthread
  • 编译后,运行可执行文件
  • ./multithread_example

时间和空间复杂度

1. 时间复杂度

  • 线程创建 (pthread_create)
  • 创建多个线程的循环运行 NUM_THREADS 次,因此线程创建的总体时间复杂度为 O(NUM_THREADS)
  • 总体时间复杂度为 O(NUM_THREADS)

2. 空间复杂度

  • 存储线程 ID 所需的空间与线程数 (NUM_THREADS) 成正比。因此,这些数组的空间复杂度为 O(NUM_THREADS)。
  • 总体空间复杂度为 O(NUM_THREADS)。

pthread_self() 的优点

C 语言中的 pthread_self() 函数有几个优点。一些主要优点如下:

  1. 可移植性:通过使用标准化 API,用户可以在不同的类 Unix 系统上开发多线程服务,这些服务可由所有兼容系统共享。在新方案中,每一段代码可能涉及多个线程,即使它们部署在不同的平台上,这些线程也可以相互协作。
  2. 并行性和并发性:Pthreads 允许开发人员在一个进程中创建多个任务。由于硬件同时执行这些任务,这种并发执行模式可以提高多核系统的性能。
  3. 可伸缩性:Pthreads 多线程允许应用程序随着硬件资源的变化而扩展。因此,程序中的任务可以划分为多个线程,然后在不同的处理器或核心上运行。在某些情况下,这样做可以使某些类型的程序运行得更快,从而总体上提高其性能。
  4. 灵活的线程创建:使用 Pthreads,开发人员可以设计自己的线程创建例程以满足其需求。例如,当开发人员创建线程时,他可以设置堆栈大小、调度策略和线程优先级。这有助于微调特定的应用程序要求。
  5. 线程安全函数:Pthreads 还提供了许多标准 C 库函数的线程安全版本。在这些情况下,函数被称为 “线程安全”,因为它能防止数据损坏或死锁等问题。多个线程共享公共资源并使用这些类型的函数而不会出现任何问题。

pthread_self() 的应用

C 语言中的 pthread_self() 函数有几个应用。一些主要应用如下:

  1. 服务器:Web 服务器、数据库服务器等服务器应用程序通常使用线程 (pthreads) 来并发处理多个客户端请求。因此,每个传入连接都可以视为一个单独的线程,从而允许单个服务器在任何给定时刻服务多个客户端。
  2. 多进程文件系统:Pthreads 允许多个用户或进程同时访问其文件,这可以显著提高文件 I/O 操作的效率。与分布式文件系统和文件服务器相比,许多文件系统支持此基本功能。
  3. 网络编程:线程可以为聊天服务器、消息传递系统和点对点应用程序等网络应用程序提供解决方案,以同时管理多个网络连接。
  4. 仿真和游戏:Pthreads 经常用于仿真软件和视频游戏。在这些程序中,物理仿真、AI 处理和渲染以及插值等并行运行,这增强了仿真或游戏对人类的真实感和响应性。
  5. 数据分析和机器学习:Pthreads 对于某些类型的问题可能会有所帮助,其中可以协作处理大量数据。例如,使用数值优化技术和统计处理的机器学习算法,或者处理大型数据库的数据分析程序。这对于作为独立子问题执行的任务特别有用。

pthread_self() 的缺点

C 语言中的 pthread_self() 函数有几个缺点。一些主要缺点如下:

  1. 平台依赖性:这是由于系统支持这些功能的不同程度以及不同操作系统上各种 POSIX 标准的合规性不同,这意味着所有细微之处都难以发现并且具有平台特定性。
  2. 复杂性和错误风险:并发编程为程序的逻辑流程引入了复杂性,尤其是在处理共享资源时。应通过正确使用互斥 (mutex) 和条件变量等同步机制来避免死锁竞态条件。最后,滥用这些构造会加速难以发现的错误。
  3. 缺乏本机 Windows 支持:Pthreads 主要为类 UNIX 系统开发。因此,Windows 不支持它。虽然 Windows 中有其他第三方实现的 pthreads,但它们不具有可移植性。
  4. 一些抽象限制:Pthreads 提供的线程模型相对低级。另一方面,开发人员必须处理许多这些方面,例如线程的创建和终止。这会导致额外的代码(也称为“样板代码实例”),这与某些其他语言中发现的高级线程模型不同。

结论

总之,使用 POSIX 线程 (pthreads) 编写的程序传输目前是为类 Unix 操作系统开发多线程应用程序的一种成熟且广泛使用的方法。该线程库具有许多优点,例如并行性、可移植性和灵活性,以及许多 RPC 系统中不那么容易存在的功能。借助 pthreads,可以在一个进程中并发执行多个任务,从而更好地利用多核平台。

总的来说,POSIX 线程仍然是 C 语言中并发编程的重要资产,它在抽象处理 POSIX 支持系统的开发人员的同时保持了一定程度的控制。只要用户仅在必要时使用此类设备,它们将极大地增强软件和系统的性能,因此它们的有效应用范围从服务器端应用程序到科学计算。