楼主: 白bai
63 0

[作业] 【毒舌笔记】多线程编程:从 “只会开线程” 到 “不翻车” 的入门指南 [推广有奖]

  • 0关注
  • 0粉丝

等待验证会员

初中生

0%

还不是VIP/贵宾

-

威望
0
论坛币
0 个
通用积分
0
学术水平
0 点
热心指数
0 点
信用等级
0 点
经验
70 点
帖子
6
精华
0
在线时间
0 小时
注册时间
2018-1-16
最后登录
2018-1-16

楼主
白bai 发表于 2025-11-18 14:48:52 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币
(本文自带毒舌效果,敏感者请谨慎阅读!看不懂算我输,学不会算你懒~)

一、多线程基础:别再把线程和进程混淆了!

【是什么】

线程(LWP,轻量级进程)就是进程的“打工人”—— 进程是“公司”(资源分配的基本单位,拥有独立内存),线程是“公司里的员工”(调度的最小单位,共享公司资源)。一个进程至少有一个“老板线程”(主线程),可以雇佣多个“员工线程”一起工作,员工之间共享办公室(进程的内存、文件句柄等),无需单独租赁场地(独立内存)。

【为什么】

为何选择多线程而非多进程?主要是为了“节省成本”: 线程切换开销比进程小10倍以上:员工换岗(线程切换)无需更换办公室,而进程切换相当于更换公司,手续繁多; 共享资源便捷:员工之间传递文件(数据)可以直接放在办公桌上(共享内存),无需通过快递(IPC); 适合多任务并行:例如一个线程“接听电话”(处理网络请求),另一个线程“编写文档”(处理数据),互不影响。

【怎么用】(基础概念无需编写代码,但需牢记)

核心记住3句话: 线程共享进程的资源(栈、堆、静态区除外,每个线程有自己的栈); 主线程结束即意味着整个公司的解散,所有员工(线程)也随之结束; 线程执行顺序随机(采用时间片轮转),谁先抢占CPU谁先执行,不可期望按创建顺序执行。

【坑在哪】

线程不安全:多个员工争用同一台打印机(共享资源),若不排队将导致打印混乱(数据竞争); 一个线程崩溃可能影响整个进程:某员工翻桌(线程访问非法内存),可能导致整个公司(进程)崩溃; 不要以为线程越多越快:线程过多会导致频繁切换(上下文切换开销),反而变慢,这称为“线程爆炸”,类似于公司招聘过多员工导致内部消耗。 关键词:线程、进程、共享资源、调度、并行

二、多线程编程(重点):这些函数让你少走99%的弯路

2.1 线程创建:pthread_create—— 雇佣员工工作

【是什么】

pthread_create
是“招聘员工”的核心函数,专门用于创建子线程(员工),使其执行指定的“工作任务”(线程函数)。

【为什么】

总不能让老板(主线程)独自完成所有任务吧?例如你编写一个下载软件,主线程负责界面,子线程负责下载,否则界面会卡死,用户可能认为程序崩溃,直接卸载。

【怎么用】(3种场景,代码可以直接运行,不要随意修改!)

场景1:无参线程(员工执行固定任务,无需额外信息) cpp 运行
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

// 线程函数:员工的“工作内容”(必须是void*返回值、void*参数)
void* worker(void* arg) {
    while (1) {
        printf("分支线程:摸鱼中...(线程号:%#lx)\n", pthread_self());
        sleep(1); // 摸鱼1秒,别让CPU炸了
    }
}

int main() {
    pthread_t tid; // 员工工号(线程号)
    // 招聘员工:参数依次是(工号地址、线程属性(NULL=默认)、工作内容、参数(NULL=无参))
    if (pthread_create(&tid, NULL, worker, NULL) != 0) {
        printf("线程创建失败!你这招聘流程有问题啊~\n");
        return -1;
    }
    printf("主线程:招聘成功!员工工号:%#lx\n", tid);

    // 老板自己也得干活,不然主线程结束,员工直接失业
    while (1) {
        printf("主线程:监督摸鱼...\n");
        sleep(1);
    }
    return 0;
}
编译命令(忘记添加-lpthread将会报错):
g++ -o no_arg_thread no_arg_thread.cpp -lpthread
场景2:传递单个数据(给员工分配任务目标) cpp 运行
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* worker(void* arg) {
    // void*转具体类型(别瞎转!转错直接段错误,哭都来不及)
    int task_num = *(int*)arg; 
    printf("分支线程:收到任务!要干%d件活\n", task_num);
    pthread_exit(NULL); // 干完活下班,别赖着
}

int main() {
    pthread_t tid;
    int num = 5; // 任务指标:干5件活
    // 传参:把num的地址传进去(注意:别传局部变量的临时值!)
    if (pthread_create(&tid, NULL, worker, &num) != 0) {
        printf("线程创建失败,滚去查编译命令!\n");
        return -1;
    }
    pthread_join(tid, NULL); // 等员工干完活再下班
    printf("主线程:任务完成,发工资!\n");
    return 0;
}
场景3:传递多个数据(给员工分配完整任务包:编号、名称、工作量) cpp 运行
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <unistd.h>

// 任务包(结构体是多线程传多个数据的标准答案,别用全局变量,显得你很菜)
struct Task {
    int id;         // 任务编号
    char name[20];  // 任务名字
    int workload;   // 工作量
};

void* worker(void* arg) {
    // 结构体指针转换(记住这个套路,面试常考!)
    struct Task* task = (struct Task*)arg;
    printf("分支线程:任务ID:%d,任务名:%s,工作量:%d\n", 
           task->id, task->name, task->workload);
    pthread_exit(NULL);
}

int main() {
    pthread_t tid;
    struct Task task = {1001, "写BUG", 3}; // 任务:写3个BUG(程序员的日常)
    if (pthread_create(&tid, NULL, worker, &task) != 0) {
        printf("创建失败?建议检查是不是没装pthread库~\n");
        return -1;
    }
    pthread_join(tid, NULL);
    printf("主线程:BUG写得好,扣绩效!\n");
    return 0;
}

【坑在哪】

编译时忘记添加
-lpthread
:会报“未定义引用”的错误,纯属自找麻烦; 线程函数签名不正确:必须是
void* (*start_routine)(void*)
,返回值和参数都不能错; 传递局部变量地址:主线程完成后变量被销毁,子线程访问野指针,程序直接崩溃; 主线程提前退出:员工仍在工作,老板却走了,进程直接终止,线程的努力白费。 关键词:pthread_create、线程函数、void * 传参、-lpthread、结构体传参

2.2 线程号:pthread_self—— 查询员工工号

【是什么】

pthread_self()
是“员工查询自己工号”的函数,返回当前线程的ID,无需传递参数,永远成功(比你上班打卡还可靠)。

【为什么】

用于调试!多个线程同时运行,你需要知道哪个是哪个?打印线程号就能区分“谁在偷懒,谁在工作”。

【怎么用】

cpp 运行
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* worker(void* arg) {
    // 线程自己查工号,比主线程传过来靠谱
    printf("分支线程:我的工号是%#lx\n", pthread_self());
    pthread_exit(NULL);
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, worker, NULL);
    printf("主线程:我查的员工工号是%#lx\n", tid); // 主线程记录的工号
    pthread_join(tid, NULL);
    return 0;
}

【坑在哪】

别将
pthread_t
当作int类型打印:线程号是无符号长整型,使用
%#lx
格式,否则会打印一堆乱码,误以为程序出问题。 关键词:pthread_self、线程ID、%#lx

2.3 线程退出:pthread_exit—— 员工下班

【是什么】

pthread_exit(void* retval)
是“员工主动下班”的函数,参数是退出状态(通常填NULL,没有人关心你是如何下班的)。

【为什么】

线程完成任务后应主动退出,不要占用进程资源!例如下载线程下载完文件后直接退出,比让主线程催促更优雅。

【怎么用】

cpp 运行
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* worker(void* arg) {
    printf("分支线程:干活ing...\n");
    sleep(2);
    pthread_exit(NULL); // 干完活下班,不拖沓
    // 下面的代码永远执行不到,别写废话
    printf("我还没走!");
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, worker, NULL);
    pthread_join(tid, NULL);
    printf("主线程:员工下班,我也溜了~\n");
    return 0;
}

【坑在哪】

不要在主线程中使用
pthread_exit
:主线程退出,整个进程也会结束,其他线程无论是否完成任务都会随之终止,相当于老板逃跑,员工集体失业。 关键词:pthread_exit、线程退出、NULL

2.4 线程回收:pthread_join—— 老板等待员工下班

【是什么】

pthread_join(pthread_t thread, void** retval)
是“老板等待员工下班”的函数,阻塞主线程,直到指定线程退出,还可以回收线程资源(避免僵尸线程)。

【为什么】

你招聘员工工作,不等他完成就关门大吉?未回收的线程会变成“僵尸线程”,占用系统资源却不工作,就像职场上的老油条一样令人讨厌。

【怎么用】

cpp 运行
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>

void* worker(void* arg) {
    printf("分支线程:加班ing...\n");
    sleep(3);
    printf("分支线程:下班!\n");
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, worker, NULL);
    printf("主线程:等员工加班完...\n");
    pthread_join(tid, NULL); // 阻塞等,员工不下班我不走
    printf("主线程:员工下班,我也下班!\n");
    return 0;
}

【坑在哪】

忽略
pthread_join

主线程过早退出,线程资源未释放,僵尸线程累积,系统资源被占用;

回收已分离的线程:线程设为分离状态后,不应使用

pthread_join

回收,否则会出错,纯属多余。

关键词:pthread_join、阻塞回收、僵尸线程、资源回收

2.5 线程分离态:pthread_detach—— 员工自动结算????

【是什么】

pthread_detach(pthread_t thread)

是 “员工离职后自动结算” 的函数,将线程设置为分离状态,退出后系统自动回收资源,无需主线程

pthread_join

监控。

【为什么】

有些线程完成任务后不再有用,无需主线程等待回收,例如日志线程,写完日志直接退出,系统自动清理,避免主线程操心。

【怎么用】

cpp

运行

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

void* worker(void* arg) {
    printf("分支线程:分离态干活,干完自动溜~\n");
    sleep(2);
    printf("分支线程:溜了溜了~\n");
    pthread_exit(NULL);
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, worker, NULL);
    pthread_detach(tid); // 设为分离态,不用等它下班
    printf("主线程:不管员工了,我先摸鱼...\n");
    sleep(3); // 给员工足够时间干活,别主线程先溜了
    printf("主线程:我也溜了~\n");
    return 0;
}

【坑在哪】??

分离状态的线程无法回收:设为分离状态后,再使用

pthread_join

会出错,别自找麻烦;

主线程提前退出:分离状态的线程仍在运行,主线程提前退出,线程仍会被终止,徒劳无功。

关键词:pthread_detach、分离状态、自动回收、非阻塞

【毒舌总结】

多线程基础就这么点内容,记住:

线程是进程的 “工作者”,共享资源但不安全;

核心函数只有 5 个:

pthread_create

(招聘)、

pthread_self

(查询工号)、

pthread_exit

(下班)、

pthread_join

(等待下班)、

pthread_detach

(自动结算);

编译必须加上

-lpthread

,传递参数时不要随意转换类型,回收时不要遗漏,分离状态不要滥用;

不要一开始就搞同步互斥,先掌握 “线程创建 - 传参 - 回收”,否则同步互斥只会让你死得更惨~

复习时按关键词查找,代码直接修改即可使用,再出错你就该反思自己是否不适合编程(bushi)~ 下次学习同步互斥,我会详细吐槽那些让你崩溃的竞争条件和死锁!

多线程编程复习查找表(毒舌速查版)

(按 “关键词搜索→瞬间理解核心” 设计,找不到算表格设计差,记不住算你懒~)

模块 关键词 核心内容(人话总结) 关键代码片段(精简版) 坑点 & 注意事项 ??
多线程基础 线程 vs 进程 进程 = 公司(资源分配单位),线程 = 员工(调度单位);线程共享进程资源,切换开销小,不安全。 - 1. 线程崩溃可能导致整个进程崩溃;
2. 多线程争夺资源必定混乱(竞争);
3. 主线程退出 = 全部线程结束。
多线程基础 线程执行顺序 随机!按 CPU 时间片轮询,谁抢到谁执行,不要期望按创建顺序来。 - 不要编写 “线程 1 先执行,线程 2 后执行” 的逻辑,纯粹是自找麻烦。
多线程编程 - 创建 pthread_create、-lpthread 雇佣员工干活,参数:(工号地址,默认属性 NULL,工作函数,传参);编译必须加 - lpthread。
cpp<br>pthread_t tid;<br>pthread_create(&tid, NULL, worker, &arg); // 雇人<br>g++ xxx.cpp -o xxx -lpthread // 编译<br>
1. 函数签名必须是
void* (*)(void*)
;2. 传递局部变量地址 = 野指针;3. 忘记加 - lpthread 必报错。
多线程编程 - 线程号 pthread_self、%#lx 线程查询自己的工号,返回无符号长整型,打印用 %#lx。
cpp<br>void* worker(void* arg){<br> printf("工号:%#lx\n", pthread_self()); // 查工号<br>}<br>
不要用 % d 打印!线程号是长整型,错误打印会导致垃圾值。
多线程编程 - 退出 pthread_exit、NULL 线程主动下班,参数填 NULL(没人关心退出状态);主线程不要使用,否则整个进程结束。
cpp<br>void* worker(void* arg){<br> sleep(2);<br> pthread_exit(NULL); // 下班溜了<br>}<br>
退出后下面的代码永远执行不到,不要写多余的代码。
多线程编程 - 回收 pthread_join、阻塞 老板等待员工下班,阻塞主线程直到线程退出,回收资源(防止僵尸线程)。
cpp<br>pthread_t tid;<br>pthread_create(&tid, NULL, worker, NULL);<br>pthread_join(tid, NULL); // 等员工下班<br>
1. 遗漏回收 = 僵尸线程累积;
2. 回收分离状态的线程 = 出错。
多线程编程 - 分离态 pthread_detach、自动回收 设置为分离状态,线程退出后系统自动回收,无需 join;适用于 “完成即走” 的线程(如日志线程)。
cpp<br>pthread_t tid;<br>pthread_create(&tid, NULL, worker, NULL);<br>pthread_detach(tid); // 自动清算<br>
1. 分离后不能 join;
2. 主线程不要提前退出,否则线程白干。
多线程编程 - 传参 单数据、结构体传参 单数据传地址,多数据用结构体(不要用全局变量!);转换时必须强制转换对应类型。
cpp<br>// 结构体传参<br>struct Task{int id; char name[20];};<br>pthread_create(&tid, NULL, worker, &task); // 传结构体地址<br>
1. 传递局部变量 = 野指针;
2. 强制转换类型错误 = 段错误(哭都来不及)。
二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:入门指南 多线程 include Routine thread

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
jg-xs1
拉您进交流群
GMT+8, 2026-1-9 09:02