多线程(multithreading)使同一进程内存在多条并发执行的指令流,便于重叠计算与 I/O、利用多核处理器。在 POSIX 系统上,C 程序常通过 pthread 库创建与管理线程;线程间共享地址空间,须配合同步机制避免数据竞争。网络服务等场景常与套接字编程结合。

线程与进程

进程是资源分配的单位;线程是进程内的执行单元,同一进程的线程共享代码段与全局数据,但各自拥有独立的栈与寄存器上下文。

  • 创建与切换线程的开销通常小于进程。
  • 共享内存便于通信,也要求对共享变量进行同步。
  • 多线程可在多核上并行执行。
#include <pthread.h>
#include <stdio.h>

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

int main(void) {
    pthread_t thread;

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

    return 0;
}

链接时需指定线程库,例如:

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);

参数依次为线程标识符、属性(常传 NULL)、入口函数及传给入口函数的参数。入口函数须为 void *(*)(void *) 类型。

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

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

int main(void) {
    pthread_t thread1;

    pthread_create(&thread1, NULL, foo, NULL);
    return 0;
}

若主线程在子线程完成前退出且未 join,子线程输出可能无法观察到。

2. pthread_join()

阻塞调用线程,直至指定线程结束:

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

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

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

    pthread_join(thread1, NULL);

    return 0;
}

3. pthread_exit()

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

    pthread_exit(NULL);

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

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(void) {
    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()

#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(void) {
    pthread_t thread1;
    pthread_create(&thread1, NULL, foo, NULL);
    pthread_join(thread1, NULL);
    return 0;
}

并发问题

  • 竞态条件:多线程无同步地读写同一变量,结果依赖调度顺序。
  • 死锁:线程相互等待对方持有的锁,均无法继续。
  • 饥饿:某线程长期无法获得所需资源。

同步机制概览

机制作用
互斥锁(Mutex)保证临界区同一时刻仅一线程进入
信号量(Semaphore)控制可同时访问资源的线程数
条件变量在条件满足前阻塞线程
屏障等待所有参与线程到达同步点
读写锁允许多读单写

互斥锁保护共享计数器

#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(void) {
    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;
}

两线程各递增一百万次,有互斥保护时结果通常为 2000000;若去掉锁,counter++ 非原子,结果可能偏小。

利弊

多线程可提高吞吐与响应性,但增加设计与调试复杂度,须明确共享数据边界并选用适当同步。异步 I/O 与信号处理在并发程序中也需一并考虑。

本节介绍了 pthread 的基本用法与同步需求。下一节说明基于套接字的网络编程流程。