多线程(multithreading)是让一个进程里同时跑多段独立代码的技术。它能让程序一边算数据、一边刷界面,把多核 CPU 用起来。C 语言里最常用的是 POSIX 线程库(pthread),下面从它讲起。

线程是什么

进程是资源分配的基本单位,线程是进程里的一条执行流。一个进程可以开很多线程,它们共享进程的代码、数据、文件,但各自有独立的程序计数器、寄存器和栈。

  • 线程比进程轻量,创建和切换更快。
  • 共享内存让线程间通信方便,但也带来了同步问题。
  • 多个线程可以在多核上真正并行执行。

一个最简单的例子,创建线程跑一段代码:

#include <pthread.h>
#include <stdio.h>

void* foo(void* arg) {
    printf("Thread is running\n");
    return NULL;
}

int main() {
    pthread_t thread;

    pthread_create(&thread, NULL, foo, NULL);
    pthread_join(thread, NULL);

    return 0;
}

输出:

Thread is running

pthread_create() 开一条新线程跑 foo(),主线程用 pthread_join() 等它跑完再继续。编译时记得加 -lpthread:

gcc program.c -lpthread -o program

pthread 核心 API

1. pthread_create():创建线程

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

四个参数依次是:存线程 ID 的变量、线程属性(一般填 NULL 用默认)、线程要执行的函数、传给这个函数的参数。线程函数签名固定是 void* 返回、void* 参数:

#include <pthread.h>
#include <stdio.h>

void* foo(void* arg) {
    printf("Created a new thread");
    return NULL;
}

int main() {
    pthread_t thread1;

    /* 创建一条线程执行 foo() */
    pthread_create(&thread1, NULL, foo, NULL);
    return 0;
}

注意:这个程序没加 join,主线程可能先结束,子线程还没来得及打印。

2. pthread_join():等待线程结束

pthread_join() 会阻塞调用它的线程,直到指定的线程终止。主线程通常在 main 结束前 join 所有子线程,避免子线程没跑完程序就退了:

#include <pthread.h>
#include <stdio.h>

void* foo(void* arg) {
    printf("Thread is running.\n");
    return NULL;
}

int main() {
    pthread_t thread1;
    pthread_create(&thread1, NULL, foo, NULL);

    /* 等线程跑完 */
    pthread_join(thread1, NULL);

    return 0;
}

3. pthread_exit():主动结束线程

线程函数里调用 pthread_exit() 立即终止当前线程,后面的代码不再执行:

void* foo(void* arg) {
    printf("Thread is running.\n");

    pthread_exit(NULL);

    printf("This statement never executes.\n");
}

输出:

Thread is running.

4. pthread_cancel():请求取消线程

从外面请求结束一条线程。能不能立刻生效取决于线程是否处于可取消状态。常用于让某个一直在忙的线程停下来:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void* myThreadFunc(void* arg) {
    while (1) {
        printf("Thread is running...\n");
        sleep(1);
    }
    return NULL;
}

int main() {
    pthread_t thread;
    pthread_create(&thread, NULL, myThreadFunc, NULL);
    sleep(5);

    /* 请求取消这条线程 */
    pthread_cancel(thread);
    /* 等它终止 */
    pthread_join(thread, NULL);

    printf("Main thread finished.\n");
    return 0;
}

5. pthread_self():获取当前线程 ID

线程内部可以用 pthread_self() 拿到自己的 ID,方便区分是哪个线程在干活:

#include <pthread.h>
#include <stdio.h>

void* foo(void* arg) {
    pthread_t thisThread = pthread_self();
    printf("Current thread ID: %lu\n",
           (unsigned long)thisThread);
    return NULL;
}

int main() {
    pthread_t thread1;
    pthread_create(&thread1, NULL, foo, NULL);
    pthread_join(thread1, NULL);
    return 0;
}

多线程的常见误区

  • 竞态条件(Race Condition):多个线程同时读写同一份共享数据,结果不可预测。比如两个线程同时给同一个计数器 +1,可能丢更新。
  • 死锁(Deadlock):两个线程互相等待对方手里的资源,谁也动不了。线程 A 拿着锁 1 等锁 2,线程 B 拿着锁 2 等锁 1。
  • 饥饿(Starvation):某条线程一直抢不到资源被晾在一边,低优先级线程可能被高优先级线程活活饿死。

线程同步

同步是为了保证共享资源一次只被一个线程安全使用。常用机制:

机制作用
互斥锁(Mutex)同一时刻只允许一个线程进入临界区
信号量(Semaphore)用计数器控制同时访问资源的线程数量
条件变量(Condition Variable)让线程在某个条件满足前挂起等待
屏障(Barrier)所有线程到齐才一起放行
读写锁(Read-Write Lock)允许多个读者、只允许一个写者

用互斥锁保护计数器

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

int counter = 0;              /* 共享变量 */
pthread_mutex_t lock;         /* 互斥锁 */

void* increment(void* arg) {
    for (int i = 0; i < 1000000; i++) {
        pthread_mutex_lock(&lock);
        counter++;            /* 临界区:一次一个线程进 */
        pthread_mutex_unlock(&lock);
    }
    return NULL;
}

int main() {
    pthread_t t1, t2;
    pthread_mutex_init(&lock, NULL);

    pthread_create(&t1, NULL, increment, NULL);
    pthread_create(&t2, NULL, increment, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    printf("counter = %d\n", counter);
    pthread_mutex_destroy(&lock);
    return 0;
}

两条线程各加 100 万次,有锁保护结果是 2000000。要是把锁去掉,因为 +1 不是原子操作,结果通常会小于 2000000,这就是竞态。

多线程的利与弊

优点:

  • 提升性能、更好利用 CPU,多核机器上优势明显。
  • 程序响应更及时,界面不卡顿。
  • 资源共享高效,扩展性好。

缺点:

  • 设计和实现复杂度高。
  • 必须小心同步,否则引入竞态、死锁。
  • 线程创建、调度、管理有额外开销。

多线程本质是用复杂度换性能。先想清楚哪些资源要共享、共享时怎么锁,再动手开线程。