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在新标准中已被标记为废弃,可改用nanosleep或clock_nanosleep获得更精确的休眠。
相关推荐
geats人山人海8 分钟前
linux 2 基本操作与vim
linux·运维·vim
常州晟凯电子科技12 分钟前
sigmaStar SSC305DE/SSC308开发笔记之开发环境搭建和SDK编译
人工智能·笔记·嵌入式硬件·物联网·性能优化·视频编解码
沐欣工作室_lvyiyi13 分钟前
基于单片机的跑步机控制系统设计(论文+源码)
c语言·单片机·嵌入式硬件·课程设计·keil·跑步机设计
苏辰澈17 分钟前
C++自己的族谱规则:继承详解下
c语言·开发语言·c++·visualstudio
她说彩礼65万26 分钟前
C语言 const的使用
java·服务器·c语言
欣欣之王来了40 分钟前
前端开发学习路线图:2026年Vue方向最新版
前端·学习·架构·项目·vue3教程
wdfk_prog44 分钟前
LWIP教程 00:从网线上的电信号到 lwIP——PHY、MAC、DMA 与 `netif` 的完整边界
运维·网络·笔记·学习·ip
小黄蚁1 小时前
无人驾驶入门笔记(一)
笔记·自动驾驶·信息与通信
py有趣1 小时前
硬件SPI和软件模拟SPI在配置方面的一点区别
c语言·stm32·单片机
lisanmengmeng1 小时前
NRPE 添加命令(三)
java·linux·服务器