一、 pthread_create(3) Linux 手册页:

man7.org > Linux > man-pages
Linux/UNIX 系统编程培训
pthread_create(3) — Linux 手册页

名称 (NAME) | 库 (LIBRARY) | 概要 (SYNOPSIS) | 描述 (DESCRIPTION) | 返回值 (RETURN VALUE) | 错误 (ERRORS) | 属性 (ATTRIBUTES) | 标准 (STANDARDS) | 历史 (HISTORY) | 注解 (NOTES) | 缺陷 (BUGS) | 示例 (EXAMPLES) | 参见 (SEE ALSO) | 跋 (COLOPHON)


名称 (NAME)

pthread_create - 创建一个新线程

库 (LIBRARY)

POSIX 线程库 (libpthread, -lpthread)

概要 (SYNOPSIS)

#include <pthread.h>

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

描述 (DESCRIPTION)

pthread_create() 函数在调用它的进程中启动一个新线程。新线程通过调用 start_routine() 函数开始执行;arg 作为唯一参数传递给 start_routine()

新线程将以下列方式之一终止:

  • 它调用 pthread_exit(3),并指定一个退出状态值。同一进程中调用 pthread_join(3) 的另一个线程可以获取此值。
  • 它从 start_routine() 中返回。这等同于使用 return 语句中提供的值来调用 pthread_exit(3)
  • 它被取消(参见 pthread_cancel(3))。
  • 进程中的任何线程调用了 exit(3),或者主线程从 main() 中返回。这会导致进程中的所有线程终止。

attr 参数指向一个 pthread_attr_t 结构体,该结构体的内容在创建线程时用于决定新线程的属性;此结构体使用 pthread_attr_init(3) 及相关函数进行初始化。如果 attr 为 NULL,则使用默认属性创建线程。

在成功返回之前,pthread_create() 会将新线程的 ID 存储在由 thread 指向的缓冲区中;此标识符在后续调用其他 pthreads 函数时用于指代该线程。

新线程会继承创建它的线程的信号掩码副本 (pthread_sigmask(3))。新线程的挂起信号集为空 (sigpending(2))。新线程不会继承创建者的备用信号栈 (sigaltstack(2))。

新线程会继承调用线程的浮点环境 (fenv(3))。

新线程的 CPU 时间时钟初始值为 0(参见 pthread_getcpuclockid(3))。

Linux 专有细节

新线程继承调用线程的权能集合(capability sets,参见 capabilities(7))和 CPU 亲和力掩码(CPU affinity mask,参见 sched_setaffinity(2))的副本。

返回值 (RETURN VALUE)

成功时,pthread_create() 返回 0;出错时,它返回一个错误编号(error number),并且 *thread 的内容处于未定义状态。

错误 (ERRORS)

  • EAGAIN:系统资源不足,无法创建另一个线程。
  • EAGAIN:遇到了系统对线程数量施加的限制。可能触发此错误的限制包括:达到了由 setrlimit(2) 设置的实际用户 ID 所能拥有的进程和线程数的软限制 (RLIMIT_NPROC);达到了内核系统级最大进程和线程数限制 /proc/sys/kernel/threads-max(参见 proc(5));或达到了 PID 的最大数量限制 /proc/sys/kernel/pid_max(参见 proc(5))。
  • EINVALattr 中的设置无效。
  • EPERM:没有权限设置 attr 中指定的调度策略和参数。

属性 (ATTRIBUTES)

有关本节中使用的术语解释,请参见 attributes(7)

接口 属性
pthread_create() 线程安全性 (Thread safety) MT-Safe (多线程安全)

标准 (STANDARDS)

POSIX.1-2008.

历史 (HISTORY)

POSIX.1-2001.

注解 (NOTES)

有关 pthread_create() 返回在 *thread 中的线程 ID 的进一步信息,请参见 pthread_self(3)。除非使用了实时调度策略,否则在调用 pthread_create() 之后,接下来哪个线程(是调用者还是新线程)将被执行是不确定的。

线程可以是可连接的 (joinable)分离的 (detached)。如果一个线程是可连接的,那么另一个线程可以调用 pthread_join(3) 来等待该线程终止并获取其退出状态。只有当已终止的可连接线程被连接 (joined) 后,其最后剩下的系统资源才会被释放回系统。当一个分离的线程终止时,其资源会自动释放回系统:无法连接分离的线程来获取其退出状态。将线程设为分离状态对于某些后台守护 (daemon) 线程非常有用,因为应用程序不需要关心它们的退出状态。默认情况下,新线程是在可连接状态下创建的,除非 attr 被设置为在分离状态下创建线程(使用 pthread_attr_setdetachstate(3))。

在 NPTL 线程实现下,如果在程序启动时 RLIMIT_STACK 软资源限制的任何值非 “unlimited”(无限制),那么它将决定新线程的默认栈大小。通过使用 pthread_attr_setstacksize(3),可以在用于创建线程的 attr 参数中显式设置栈大小属性,以获得不同于默认值的栈大小。如果 RLIMIT_STACK 资源限制被设置为 “unlimited”,则会使用特定于架构的值作为栈大小:大多数架构为 2 MB;POWER 和 Sparc-64 架构为 4 MB。

缺陷 (BUGS)

在已废弃的 LinuxThreads 实现中,同一个进程中的每个线程都有不同的进程 ID。这违反了 POSIX 线程规范,也是该实现中许多其他不符合标准问题的根源;参见 pthreads(7)

示例 (EXAMPLES)

下面的程序演示了 pthread_create() 以及 pthreads API 中一些其他函数的使用方法。

在以下运行示例中(在提供 NPTL 线程实现的系统上),栈大小默认为 “stack size” (栈大小) 资源限制所给定的值:

$ ulimit -s
8192            # 栈大小限制为 8 MB (0x800000 字节)
$ ./a.out hola salut servus
Thread 1: top of stack near 0xb7dd03b8; argv_string=hola
Thread 2: top of stack near 0xb75cf3b8; argv_string=salut
Thread 3: top of stack near 0xb6dce3b8; argv_string=servus
Joined with thread 1; returned value was HOLA
Joined with thread 2; returned value was SALUT
Joined with thread 3; returned value was SERVUS

在下一次运行中,程序显式地为创建的线程设置了 1 MB 的栈大小(使用 pthread_attr_setstacksize(3)):

$ ./a.out -s 0x100000 hola salut servus
Thread 1: top of stack near 0xb7d723b8; argv_string=hola
Thread 2: top of stack near 0xb7c713b8; argv_string=salut
Thread 3: top of stack near 0xb7b703b8; argv_string=servus
Joined with thread 1; returned value was HOLA
Joined with thread 2; returned value was SALUT
Joined with thread 3; returned value was SERVUS

程序源码

#include <ctype.h>
#include <err.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

struct thread_info {    /* 用于作为 thread_start() 的参数 */
    pthread_t thread_id;        /* 由 pthread_create() 返回的 ID */
    int       thread_num;       /* 应用程序定义的线程编号 */
    char     *argv_string;      /* 来源于命令行参数 */
};

/* 线程启动函数:显示栈顶附近的地址,
   并返回 argv_string 的大写副本。 */

static void *
thread_start(void *arg)
{
    struct thread_info *tinfo = arg;
    char *uargv;

    printf("Thread %d: top of stack near %p; argv_string=%s\n",
           tinfo->thread_num, (void *) &tinfo, tinfo->argv_string);

    uargv = strdup(tinfo->argv_string);
    if (uargv == NULL)
        err(EXIT_FAILURE, "strdup");

    for (char *p = uargv; *p != '\0'; p++)
        *p = toupper(*p);

    return uargv;
}

int
main(int argc, char *argv[])
{
    int                 s, opt;
    void                *res;
    size_t              num_threads;
    ssize_t             stack_size;
    pthread_attr_t      attr;
    struct thread_info  *tinfo;

    /* "-s" 选项为我们的线程指定栈大小。 */

    stack_size = -1;
    while ((opt = getopt(argc, argv, "s:")) != -1) {
        switch (opt) {
        case 's':
            stack_size = strtoul(optarg, NULL, 0);
            break;

        default:
            fprintf(stderr, "Usage: %s [-s stack-size] arg...\n",
                    argv[0]);
            exit(EXIT_FAILURE);
        }
    }

    num_threads = argc - optind;

    /* 初始化线程创建属性。 */

    s = pthread_attr_init(&attr);
    if (s != 0)
        errc(EXIT_FAILURE, s, "pthread_attr_init");

    if (stack_size > 0) {
        s = pthread_attr_setstacksize(&attr, stack_size);
        if (s != 0)
            errc(EXIT_FAILURE, s, "pthread_attr_setstacksize");
    }

    /* 为 pthread_create() 的参数分配内存。 */

    tinfo = calloc(num_threads, sizeof(*tinfo));
    if (tinfo == NULL)
        err(EXIT_FAILURE, "calloc");

    /* 为每个命令行参数创建一个线程。 */

    for (size_t tnum = 0; tnum < num_threads; tnum++) {
        tinfo[tnum].thread_num = tnum + 1;
        tinfo[tnum].argv_string = argv[optind + tnum];

        /* pthread_create() 调用将线程 ID 存储到对应的 tinfo[] 元素中。 */

        s = pthread_create(&tinfo[tnum].thread_id, &attr,
                           &thread_start, &tinfo[tnum]);
        if (s != 0)
            errc(EXIT_FAILURE, s, "pthread_create");
    }

    /* 销毁线程属性对象,因为它不再需要了。 */

    s = pthread_attr_destroy(&attr);
    if (s != 0)
        errc(EXIT_FAILURE, s, "pthread_attr_destroy");

    /* 现在连接 (join) 每个线程,并显示其返回值。 */

    for (size_t tnum = 0; tnum < num_threads; tnum++) {
        s = pthread_join(tinfo[tnum].thread_id, &res);
        if (s != 0)
            errc(EXIT_FAILURE, s, "pthread_join");

        printf("Joined with thread %d; returned value was %s\n",
               tinfo[tnum].thread_num, (char *) res);
        free(res);      /* 释放由线程分配的内存 */
    }

    free(tinfo);
    exit(EXIT_SUCCESS);
}

参见 (SEE ALSO)

getrlimit(2), pthread_attr_init(3), pthread_cancel(3), pthread_detach(3), pthread_equal(3), pthread_exit(3), pthread_getattr_np(3), pthread_join(3), pthread_self(3), pthread_setattr_default_np(3), pthreads(7)

跋 (COLOPHON)

本页面是 man-pages(Linux 内核与 C 库用户空间接口文档)项目的一部分。关于该项目的信息可以前往 ⟨https://www.kernel.org/doc/man-pages/⟩ 了解。如果您发现本手册页有漏洞或错误,请参阅 ⟨https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/tree/CONTRIBUTING⟩。
此页面是从 man-pages-6.18.tar.gz 压缩包中获取的,该包于 2026-05-24 获取自 ⟨https://mirrors.edge.kernel.org/pub/linux/docs/man-pages/⟩。如果您在此 HTML 版本的页面中发现任何渲染问题,或者您认为有更好、更新的页面源码,又或者您想要对本跋(此部分不属于原始手册页)中的信息提供更正或改进,请发送邮件至 man-pages@man7.org。


Linux man-pages 6.18 | 2026-02-10 | pthread_create(3)
引用了此页面的其他页面:mmap(2), spu_run(2), wait(2), pthread_attr_init(3), pthread_attr_setdetachstate(3), pthread_attr_setguardsize(3), pthread_attr_setinheritsched(3), pthread_attr_setschedparam(3), pthread_attr_setschedpolicy(3), pthread_attr_setscope(3), pthread_attr_setstack(3), pthread_attr_setstackaddr(3), pthread_attr_setstacksize(3), pthread_cancel(3), pthread_detach(3), pthread_equal(3), pthread_exit(3), pthread_getattr_default_np(3), pthread_getattr_np(3), pthread_join(3), pthread_key_create(3), pthread_self(3), pthread_setaffinity_np(3), pthread_setname_np(3), pthread_setschedparam(3), pthread_setschedprio(3), pthread_sigmask(3), pthreads(7)

HTML 渲染由 Michael Kerrisk 于 2026-05-30 创建,《The Linux Programming Interface》的作者。

二、上面示例程序的介绍

解释这段 C 多线程程序。这段程序是一个 POSIX 多线程(pthread)示例,展示了如何创建多个线程、传递参数并收集返回值。我先给你一张整体流程图,再逐部分解释。> 点击图中任意节点可深入追问某一步。
在这里插入图片描述


整体目标

这段程序的功能很简单:为每个命令行参数启动一个线程,每个线程把对应参数转成大写并返回,主线程最后打印所有结果。


各部分详解

1. 数据结构 thread_info

struct thread_info {
    pthread_t thread_id;   // pthread_create() 返回的线程句柄
    int       thread_num;  // 可读的线程编号(1, 2, 3...)
    char     *argv_string; // 指向命令行参数的指针
};

这是「线程参数包」,用于向 pthread_create() 传递多个数据。pthread_create 只接受一个 void * 参数,所以把所有信息打包成结构体是标准做法。


2. 线程函数 thread_start

static void * thread_start(void *arg)

每个线程运行的函数,做三件事:

  • 打印栈地址&tinfo 是局部变量的地址,近似反映这个线程的栈顶位置,可用来观察不同线程栈分布在内存哪里。
  • 复制字符串:用 strdup() 分配新内存(堆上),避免修改原始参数。
  • 转大写:遍历字符并调用 toupper(),最后 return uargv——这个指针就是线程的"返回值",由 pthread_join 取回。

3. main 中的参数解析

while ((opt = getopt(argc, argv, "s:")) != -1) { ... }

getopt 处理 -s <数字> 选项,允许用户自定义线程栈大小。optind 之后的参数都是要处理的字符串。


4. 线程属性设置

pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, stack_size); // 可选

pthread_attr_t 是一个配置对象,可设置栈大小、调度策略等。创建完所有线程后就 pthread_attr_destroy 了——因为属性只在 创建时 用到,之后不再需要。


5. 创建线程

pthread_create(&tinfo[tnum].thread_id, &attr, &thread_start, &tinfo[tnum]);

四个参数依次是:

参数 含义
&tinfo[tnum].thread_id 输出:存储新线程的 ID
&attr 线程属性(栈大小等)
&thread_start 线程入口函数
&tinfo[tnum] 传给入口函数的参数

6. 等待线程并收集结果

pthread_join(tinfo[tnum].thread_id, &res);
printf("... %s\n", (char *) res);
free(res);

pthread_join 有两个作用:

  • 阻塞等待:主线程暂停,直到指定线程结束。
  • 取回返回值res 接收线程 return 的指针——即线程里 strdup 分配的大写字符串。

注意,这块内存是线程分配的(在堆上),主线程取回后必须 free,否则内存泄漏。


核心要点总结

概念 说明
pthread_create 创建线程,传参通过 void *
thread_start 返回 void * 线程可以"返回值",本质是个指针
pthread_join 同步线程 + 获取返回值
strdup / free 线程在堆上分配内存,主线程负责释放
pthread_attr_t 可选配置(如自定义栈大小)

三、getopt函数

先看代码本身,再配两张图帮你理解。


代码回顾

stack_size = -1;
while ((opt = getopt(argc, argv, "s:")) != -1) {
    switch (opt) {
    case 's':
        stack_size = strtoul(optarg, NULL, 0);
        break;
    default:
        fprintf(stderr, "Usage: %s [-s stack-size] arg...\n", argv[0]);
        exit(EXIT_FAILURE);
    }
}
num_threads = argc - optind;

getopt 是什么?

getopt 是 POSIX 标准库函数,专门用来解析 Unix 风格的命令行选项(-x-s 4096 这类)。它每次调用只处理一个选项,所以要放在 while 循环里反复调用直到返回 -1(表示没有更多选项了)。

函数签名:

int getopt(int argc, char *const argv[], const char *optstring);

第三个参数 "s:"选项字符串,含义是:

  • s — 程序接受 -s 这个选项
  • : — 紧跟在 s 后面的冒号表示 -s 需要一个参数(如果不带冒号则不需要参数)

每次 getopt 返回时:

  • 返回识别到的选项字母(如 's'
  • 把该选项的参数存入全局变量 optarg
  • 遇到未知选项时返回 '?'
  • 所有选项处理完毕时返回 -1

下图展示 getopt 每轮循环在 argv 数组里的扫描过程:接下来看 while 循环每一轮的决策逻辑:—

各行代码的具体含义

stack_size = -1:初始化为 -1 作为"未设置"的哨兵值,后面用 if (stack_size > 0) 来判断用户是否提供了 -s

while ((opt = getopt(argc, argv, "s:")) != -1):这是一个赋值 + 比较合并写法。getopt 的返回值先赋给 opt,同一表达式里再与 -1 比较。等价于:

opt = getopt(argc, argv, "s:");
while (opt != -1) { ... opt = getopt(argc, argv, "s:"); }

case 's': stack_size = strtoul(optarg, NULL, 0)optarggetopt.h 提供的全局指针,自动指向当前选项的参数字符串(这里是 "4096" 这样的字符串)。strtoul 把它转换成无符号长整型,第三个参数 0 表示自动推断进制(0x 开头=十六进制,0 开头=八进制,其他=十进制)。

default: fprintf(stderr, ...); exit(EXIT_FAILURE)getopt 遇到选项字符串里没有的选项字母时返回 '?',由 default 捕获,向标准错误输出使用说明然后退出。

num_threads = argc - optindoptind 是另一个全局变量,getopt 处理完所有选项后,它指向 argv 中第一个非选项参数的下标。argc - optind 就是剩余参数的个数,也就是要创建的线程数。


一句话总结

getopt 这段代码做的事就是:-s 4096 hello world 这样的命令行拆成两部分——配置项(-s 4096)和数据(hello world,配置项交给 stack_size 保存,数据个数交给 num_threads 保存,之后用 argv[optind + tnum] 逐个访问每个数据参数。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐