Linux 软件编程学习笔记(六):线程编程入门与实践

Linux线程编程入门与实践

一、线程基础概念

1. 什么是线程?

线程可以理解为"轻量级进程"(Lightweight Process),它是一个任务从创建、调度到消亡的完整过程。与进程类似,线程也有自己的执行流,但线程是操作系统任务调度的最小单元

2. 进程与线程的区别

根据课程PDF中的总结,两者核心差异如下:

对比项 进程 线程
资源分配 操作系统资源分配的最小单元 共享进程资源,只有独立的栈空间
地址空间 独占0~4G虚拟内存(文本段、数据段、堆栈) 位于进程内部,共享文本段、数据段、堆区,仅栈独立
创建开销 大(需复制资源) 小(仅分配栈)
通信方式 进程间通信(IPC)复杂 共享全局变量、堆区,通信简单
调度 宏观并行,微观串行 同样采用时间片轮转等调度算法

关键点:线程共享进程中的全局变量、静态变量、字符串常量、堆区,因此多线程间数据同步需特别小心(使用互斥锁等)。


二、线程相关函数接口(POSIX)

在Linux下使用pthread线程库,编译时需添加-lpthread链接选项。

1. 线程创建 ------ pthread_create

c 复制代码
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);
  • 功能:在进程中创建一个新线程。
  • 参数
    • thread:用于存储线程ID(TID)的地址。
    • attr:线程属性(栈大小、调度策略等),通常传NULL使用默认属性。
    • start_routine:线程函数指针,返回void*,参数为void*
    • arg:传递给线程函数的参数。
  • 返回值:成功返回0,失败返回错误码。

2. 线程结束 ------ pthread_exit

c 复制代码
void pthread_exit(void *retval);
  • 调用此函数会终止当前线程,并将retval作为线程退出状态,供其他线程通过pthread_join获取。

3. 线程回收 ------ pthread_join

c 复制代码
int pthread_join(pthread_t thread, void **retval);
  • 功能:阻塞等待指定线程结束,并回收其资源,防止僵尸线程。
  • 参数thread为要等待的线程ID,retval用于存储线程退出状态(可为NULL)。
  • 返回值:成功返回0,失败返回错误码。

注意:线程结束必须被回收,否则会产生僵尸线程,耗尽系统资源。


三、实践:多线程打印与动态控制

我们以课程中的作业为例,实现四个线程分别以不同间隔打印信息,主线程接收键盘字符来控制各线程的"打印/暂停"状态。

需求回顾

  • 线程1(采集):每隔1秒打印"正在采集"
  • 线程2(存储):每隔2秒打印"正在存储"
  • 线程3(显示):每隔5秒打印"正在显示"
  • 线程4(日志):每隔10秒打印"正在日志记录"
  • 主进程接收 a/A 切换采集线程的打印状态,b/B 切换存储,c/C 切换显示,d/D 切换日志。

关键设计思路

  1. 线程永不退出 :使用while(1)循环,内部通过标志位判断是否执行打印及休眠。
  2. 暂停时避免忙等 :当标志位为0时,调用usleep(100000)(0.1秒)让出CPU,既省资源又能快速响应恢复指令。
  3. 主进程循环接收输入 :使用getchar()持续读取,根据字符切换对应标志位。

核心代码示例

c 复制代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>

// 全局标志,volatile防止编译器过度优化
volatile int flag1 = 1, flag2 = 1, flag3 = 1, flag4 = 1;

void *caiji(void *arg) {
    while (1) {
        if (flag1) {
            printf("采集线程(TID:%#lx) 正在采集\n", (unsigned long)pthread_self());
            sleep(1);
        } else {
            usleep(100000); // 暂停0.1秒
        }
    }
    return NULL;
}

// 其余线程函数类似,仅间隔和打印内容不同......

int main(void) {
    pthread_t tid[4];
    void *(*funcs[4])(void *) = {caiji, cunchu, xianshi, rizhi};
    char ch;

    for (int i = 0; i < 4; i++) {
        pthread_create(&tid[i], NULL, funcs[i], NULL);
    }

    while (1) {
        ch = getchar();
        switch (ch) {
            case 'a': case 'A': flag1 = !flag1; break;
            case 'b': case 'B': flag2 = !flag2; break;
            case 'c': case 'C': flag3 = !flag3; break;
            case 'd': case 'D': flag4 = !flag4; break;
            case 'q': case 'Q': // 退出程序
                for (int i = 0; i < 4; i++) {
                    pthread_cancel(tid[i]); // 或使用退出标志
                    pthread_join(tid[i], NULL);
                }
                return 0;
            default: break;
        }
    }
    return 0;
}

编译运行

bash 复制代码
gcc -o thread_demo thread_demo.c -lpthread
./thread_demo

a 可暂停/恢复采集线程的打印,按 q 退出。


四、注意事项与进阶

  1. 线程同步 :若多个线程访问共享标志,需使用互斥锁(pthread_mutex_t)避免数据竞争。
  2. 线程属性 :可通过pthread_attr_t设置线程分离状态(PTHREAD_CREATE_DETACHED),使线程结束后自动回收资源。
  3. 僵尸线程 :务必对每个创建的线程调用pthread_join,或设为分离属性。
  4. usleep的替代usleep在新标准中已被标记为废弃,可改用nanosleepclock_nanosleep获得更精确的休眠。
相关推荐
小小龙学IT1 小时前
Dear ImGui 开源即时模式 GUI 库深度解析
c++·ui·开源
摇滚侠1 小时前
《SpringBoot 3:入门与应用实战》第 8 章 AOP 的进阶机制和应用 阅读笔记 16
java·spring boot·笔记
GlueNa2SiO31 小时前
01-Flask入门与环境搭建
笔记·python·学习·flask
2401_868534781 小时前
运维工程师大厂 Nginx 面试题(二)
linux·网络协议
GGMM7891 小时前
施耐德 VZ3N1312 浪涌吸收板(整流缓冲板)
笔记·变频器·变频器维修
M78佐菲2 小时前
Linux学习笔记:文件IO
linux·笔记·学习·算法
姚永强2 小时前
zabbix安装教学
笔记·zabbix
ShineWinsu2 小时前
对于TRAE中配置Qt的解析
开发语言·c++·ide·vscode·qt·ai·trae
吃好睡好便好2 小时前
转换函数的使用
学习·matlab·生活·转换函数