线程基础:线程的概念、线程与进程的区别、线程的创建与终止、线程的属性

各位同学,今天我们来聊聊多线程编程的起点——线程基础。这部分内容我每次讲课时都会特别强调,因为它是后续所有并发技巧的根基。我自己刚入行时,就因为对线程和进程的区别理解不透彻,踩过一个挺深的坑,后面我会跟大家细说。

一、线程到底是什么?

简单来说,线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

嗯,这里要注意:线程自己不拥有系统资源,它与同属一个进程的其他线程共享进程所拥有的全部资源。说白了,线程就是「轻量级的执行路径」。

核心要点:线程是CPU调度的基本单位,进程是资源分配的基本单位。

二、线程与进程的区别——我当年踩过的坑

我记得刚工作那会儿,接手一个网络服务程序。当时项目里用了多进程模型,每个连接来了就fork一个子进程去处理。结果服务器跑了不到半小时,内存就爆了。后来老工程师看了一眼代码,说:「你试试用线程代替进程。」

替换之后,内存占用直接降了70%。为什么?因为进程之间是独立的地址空间,而线程共享同一地址空间。这就是两者最本质的区别。

对比维度 进程 线程
地址空间 独立地址空间 共享进程地址空间
资源开销 创建/切换开销大 创建/切换开销小
通信方式 IPC(管道、消息队列等) 直接读写全局变量
健壮性 一个进程崩溃不影响其他进程 一个线程崩溃可能导致整个进程退出
适用场景 强隔离、高可靠性需求 高性能、频繁通信的场景

我的个人习惯:如果任务之间需要频繁交换数据,我首选线程;如果要求高隔离性(比如不同用户的数据处理),我选进程。

三、线程的创建与终止

在C语言中,我们通常使用POSIX线程库(pthread)来操作线程。你想想看,一个线程从生到死,其实就三个步骤:创建、运行、终止。

3.1 创建线程

创建线程使用 pthread_create 函数。它的原型长这样:

#include <pthread.h>

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

参数说明:

  • thread:线程ID的指针
  • attr:线程属性(传NULL使用默认属性)
  • start_routine:线程要执行的函数
  • arg:传给线程函数的参数

来看一个最简单的例子:

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

void* print_message(void* arg) {
    char* msg = (char*)arg;
    printf("线程说:%s\n", msg);
    return NULL;
}

int main() {
    pthread_t tid;
    char* message = "Hello from thread!";
    
    if (pthread_create(&tid, NULL, print_message, message) != 0) {
        fprintf(stderr, "线程创建失败\n");
        return 1;
    }
    
    pthread_join(tid, NULL);
    return 0;
}

我曾经踩过的坑:忘记检查 pthread_create 的返回值。在资源紧张的系统上,线程创建可能失败,不检查返回值会导致后续操作全部异常。记住:任何系统调用都要检查返回值!

3.2 线程的终止

线程终止有三种方式:

  1. 自然返回:线程函数执行完毕,return返回
  2. 主动退出:调用 pthread_exit()
  3. 被动取消:其他线程调用 pthread_cancel()

我个人最推荐第一种方式——让线程函数自然返回。为什么?因为这样资源清理最干净,不容易出现内存泄漏。

void* worker(void* arg) {
    // 做一些工作...
    printf("工作完成\n");
    pthread_exit(NULL);  // 等价于 return NULL;
}

四、线程的属性——定制你的线程

线程属性用 pthread_attr_t 结构体表示。说实话,很多初学者从来不用这个,全程传NULL。但在实际项目中,定制线程属性非常有用。

常用的属性包括:

属性 说明 常用设置
detachstate 线程的分离状态 PTHREAD_CREATE_JOINABLE(可等待)
PTHREAD_CREATE_DETACHED(分离)
stacksize 线程栈大小 默认通常为8MB,可调小以节省内存
schedpolicy 调度策略 SCHED_FIFO、SCHED_RR、SCHED_OTHER

来看一个设置分离线程的例子:

pthread_attr_t attr;
pthread_t tid;

pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

if (pthread_create(&tid, &attr, worker, NULL) != 0) {
    // 处理错误
}

pthread_attr_destroy(&attr);  // 用完记得销毁

我的建议:如果你确定不需要等待线程结束(比如后台日志线程),就用分离线程。这样可以避免 pthread_join 阻塞,也省去了手动回收的麻烦。

五、知识体系总览

下面这张图是我自己整理的线程基础知识体系,你可以把它当作学习路线图:

线程基础知识体系 线程基础 线程概念 进程 vs 线程 创建与终止 线程属性 轻量级 共享资源 地址空间 切换开销 pthread_create pthread_exit 分离状态 栈大小 掌握线程基础 = 理解概念 + 会用API + 懂属性配置

六、避坑指南

最后,分享几个我这些年积累的经验:

  • 线程栈别设太小:默认8MB通常够用,但如果你在函数里声明了大数组,记得调大栈空间。我曾经因为栈溢出查了整整两天bug。
  • 主线程退出前要等待:如果主线程先退出了,整个进程就结束了,子线程会被强制终止。用 pthread_join 或者 pthread_detach 来管理线程生命周期。
  • 线程函数签名不能错:返回值必须是 void*,参数必须是 void*。这是C语言线程库的硬性规定,别想着偷懒改签名。

一句话总结:线程是轻量级的执行单元,创建快、切换快、通信方便,但需要你更小心地管理共享资源和生命周期。

公众号:蓝海资料掘金营,微信deep3321