【Linux 物联网网关主控系统-Linux主控部分(四)】

Linux 物联网网关主控系统-Linux主控部分(四)

一、引入问题

问题 1:多个进程有相同全局数组,要同步,怎么高效?

问题 2:进程有动态链表,一变化就要同步给其他进程?

问题 3:一个进程要同时处理多个阻塞任务(串口、摄像头、socket、消息队列)?

二、线程基础

1.线程的核心定义

1.线程又称轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元,是进程中的一个实体,被系统独立调度和分派。

2.线程自身不拥有独立的系统资源,仅持有运行必需的少量资源(如寄存器、堆栈),可与同属一个进程的其他线程共享进程的全部资源。

3.线程的核心作用:为程序提供并行执行的能力,允许一个程序同时执行多个任务。

2.进程与线程的调度 / 资源分配重构

1.引入线程前:进程既是资源分配的基本单位,也是调度的基本单位,各进程拥有独立的 PCB、内存空间和系统资源,上下文切换开销大。

2.引入线程后:进程仍为资源分配的基本单位,线程成为调度的基本单位;一个进程可包含多个线程,所有线程共享进程的地址空间和资源,仅做独立调度。

3.Linux 内核实现:Linux 中同样用task_struct描述线程,线程和进程参与统一的调度,无单独的线程调度机制。

3.主线程与多线程进程

1.主线程:操作系统创建进程时会自动创建一个主线程,程序的main()函数是主线程的入口,是进程的默认执行流。

2.单线程进程:程序仅有主线程,进程本身就是单一线程的执行体,包含进程的代码、数据、打开文件、单个寄存器和堆栈。

3.多线程进程:一个进程内创建多个线程,所有线程共享进程的代码、数据、打开文件等资源,每个线程拥有独立的寄存器、堆栈,可独立执行进程的不同代码部分。

4.线程的基本特性

1.任何程序至少包含一个线程,单线程程序中线程即为程序本身。

2.线程拥有三种基本状态:就绪态、阻塞态、运行态,状态切换规则与进程一致(时间片用完→就绪;遇阻塞事件→阻塞;阻塞事件结束→就绪)。

3.Linux 下多线程基于NPTL(新 POSIX 线程库) 实现,为第三方线程库提供标准操作接口。

4.线程上下文切换开销远低于进程:同进程线程共享地址空间,切换时仅需保存 / 恢复线程私有少量资源,无需重新分配内存空间等系统资源。

5、多线程与多进程的形象类比

6.线程的资源特性(核心:共享 + 私有)

(一)同进程线程共享的资源(无需额外通信即可访问)

可执行指令、静态数据、进程打开的文件描述符、信号处理函数、当前工作目录、用户 ID(UID)、用户组 ID(GID)。

(二)每个线程私有的资源(独立拥有,互不干扰)

线程 ID(TID)、PC(程序计数器)及相关寄存器、堆栈、局部变量、返回地址、错误号(errno)、信号掩码和优先级、执行状态和属性。

7.多线程的核心优点

经济实惠:分配资源少,线程切换、维护的系统开销远低于进程;

资源共享:同进程线程天然共享存储器和进程资源,通信效率高;

提升响应速度:程序某部分阻塞 / 执行冗长操作时,其他线程可继续运行;

提升多 CPU 利用率:在多处理机体系结构中,多线程实现真正的并行执行,提高并发性。

三、多线程编程-线程创建

1.核心函数

函数名 所需头文件 函数原型 参数说明 返回值
pthread_create(线程创建) #include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*routine)(void *), void *arg) 1. thread:指向线程 ID 的指针,接收创建后的线程标识 2. attr:线程属性,NULL 为默认属性 3. routine:线程执行的函数指针(新线程入口) 4. arg:传递给 routine 函数的参数 成功:0 出错:-1
pthread_join(线程等待) #include <pthread.h> int pthread_join(pthread_t thread, void **value_ptr) 1. thread:需要等待的目标线程 ID 2. value_ptr:指向指针的指针,接收线程退出的返回值(NULL 则不获取) 成功:0 出错:-1
pthread_exit(线程主动退出) #include <pthread.h> int pthread_exit(void *value_ptr) value_ptr:线程退出时的返回值,可被 pthread_join 获取 成功:0 出错:-1
pthread_cancel(线程取消) #include <pthread.h> int pthread_cancel(pthread_t thread) thread:需要被取消的目标线程 ID 成功:0 出错:-1

2.示例代码

两个线程,每隔 1 秒交替打印 A、B

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

// 线程1:打印 A
void *print_A(void *arg)
{
    while(1)
    {
        printf("A\n");
        sleep(1);  // 每隔1秒
    }
}

// 线程2:打印 B
void *print_B(void *arg)
{
    while(1)
    {
        printf("B\n");
        sleep(1);  // 每隔1秒
    }
}

int main()
{
    pthread_t tid1, tid2;

    // 创建线程1
    pthread_create(&tid1, NULL, print_A, NULL);
    // 创建线程2
    pthread_create(&tid2, NULL, print_B, NULL);

    // 等待线程
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    return 0;
}

这份代码能实现打印 A、B 的效果,但无法保证严格的 "交替"

3.线程查看

字段 全称 / 含义 核心说明
UID User ID 运行该进程 / 线程的用户 ID,标识进程的属主
PID Process ID 进程 ID,同一进程下的所有线程,PID 完全相同(线程属于该进程)
PPID Parent Process ID 父进程 ID,标识该进程的父进程
LWP Light Weight Process 轻量级进程 ID(即线程 ID/TID),同一进程下的每个线程拥有唯一 LWP;主线程的 LWP = 进程 PID
C CPU utilization CPU 占用率,标识进程 / 线程的 CPU 使用情况
NLWP Number of Light Weight Process 该进程包含的轻量级进程(线程)总数量,即进程的线程数
STIME Start Time 进程 / 线程的启动时间
TTY Terminal 该进程 / 线程运行的终端,?表示后台运行(无终端)
TIME CPU Time 进程 / 线程累计占用的 CPU 总时间
CMD Command 进程 / 线程的启动命令,同一进程下的所有线程 CMD 完全一致

四、多线程 ------ 互斥锁

1.为什么需要互斥锁

多个线程共享全局变量 / 资源时,如果同时进行读写修改,会出现数据混乱、结果错误。

这种多个线程同时操作共享资源导致的问题,叫线程竞争 / 竞态条件。

必须保证:同一时间只有一个线程访问共享资源。

互斥锁就是用来保护临界区,实现线程同步与互斥。

2.基本概念

1.临界区:访问共享资源的代码片段,必须独占执行。

2.互斥锁(mutex):一把 "锁",保证同一时刻只有一个线程进入临界区。

3.工作原理:

  • 访问前加锁
  • 访问中独占资源
  • 访问结束解锁
  • 其他线程必须等待锁释放才能进入

3.核心函数

函数名 所需头文件 函数原型 功能说明 返回值
pthread_mutex_init #include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); 初始化互斥锁 成功:0 失败:错误码
pthread_mutex_lock #include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); 加锁(阻塞等待) 成功:0 失败:错误码
pthread_mutex_unlock #include <pthread.h> int pthread_mutex_unlock(pthread_mutex_t *mutex); 解锁 成功:0 失败:错误码
pthread_mutex_destroy #include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex); 销毁互斥锁 成功:0 失败:错误码

使用步骤

1.定义互斥锁变量

pthread_mutex_t mutex;

2.初始化锁

pthread_mutex_init(&mutex, NULL);

3.进入临界区前加锁

pthread_mutex_lock(&mutex);

4.访问共享资源(临界区代码)

5.退出临界区后解锁

pthread_mutex_unlock(&mutex);

6.程序结束销毁锁

pthread_mutex_destroy(&mutex);

4.示例代码

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

// 定义互斥锁 + 顺序控制标志
pthread_mutex_t mutex;
int flag = 0;  // 0:打印A   1:打印B

// 打印 A 的线程
void *print_A(void *arg)
{
    while(1)
    {
        // 加锁
        pthread_mutex_lock(&mutex);
        
        if(flag == 0)
        {
            printf("A\n");
            flag = 1;        // 切换标志,让B打印
            sleep(1);       // 每隔1秒
        }
        
        // 解锁
        pthread_mutex_unlock(&mutex);
    }
}

// 打印 B 的线程
void *print_B(void *arg)
{
    while(1)
    {
        // 加锁
        pthread_mutex_lock(&mutex);
        
        if(flag == 1)
        {
            printf("B\n");
            flag = 0;        // 切换标志,让A打印
            sleep(1);       // 每隔1秒
        }
        
        // 解锁
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    pthread_t tid1, tid2;

    // 初始化互斥锁
    pthread_mutex_init(&mutex, NULL);

    // 创建线程
    pthread_create(&tid1, NULL, print_A, NULL);
    pthread_create(&tid2, NULL, print_B, NULL);

    // 等待线程
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    // 销毁锁
    pthread_mutex_destroy(&mutex);

    return 0;
}

这样就是严格交替打印 A、B了

PS:

互斥锁:保证同一时间只有一个线程打印,避免混乱。

flag:控制谁能打印,强制轮流,保证严格交替。

但这个其实也不是更好的,互斥锁 + 条件变量这才是线程同步的标准答案。

五、多线程-条件变量

1.为什么需要条件变量

互斥锁只能解决互斥访问问题,不能解决线程间的执行顺序、等待 / 唤醒问题。

互斥锁 + flag 会造成忙等待,大量占用 CPU,效率极低。

需要一种机制:

  • 条件不满足时,线程主动休眠等待,不占 CPU
  • 条件满足时,其他线程唤醒等待线程
    条件变量就是用来实现线程间同步、等待与唤醒的机制。

2.基本概念

1.条件变量:允许线程在满足某个条件前阻塞等待,直到其他线程发送信号唤醒。

2.必须与互斥锁配合使用:

  • 锁保护条件本身(防止并发修改条件)
  • 条件变量负责等待与唤醒

3.典型场景:生产者消费者、交替打印、队列有数据再消费等。

3.条件变量核心函数

函数名 所需头文件 函数原型 功能说明 返回值
pthread_cond_init #include <pthread.h> int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr); 初始化条件变量 成功:0 失败:错误码
pthread_cond_wait #include <pthread.h> int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); 阻塞等待条件满足,会自动释放锁 成功:0 失败:错误码
pthread_cond_signal #include <pthread.h> int pthread_cond_signal(pthread_cond_t *cond); 唤醒一个等待在该条件上的线程 成功:0 失败:错误码
pthread_cond_broadcast #include <pthread.h> int pthread_cond_broadcast(pthread_cond_t *cond); 唤醒所有等待在该条件上的线程 成功:0 失败:错误码
pthread_cond_destroy #include <pthread.h> int pthread_cond_destroy(pthread_cond_t *cond); 销毁条件变量 成功:0 失败:错误码

pthread_cond_wait 关键特性

调用时会自动释放互斥锁,让其他线程能进入临界区修改条件。

被唤醒时会自动重新加锁,保证条件判断安全。

必须在已加锁的情况下调用。

必须配合while判断条件(防止虚假唤醒)。

4.示例代码

条件变量 = 让 "暂时不能打印" 的线程去睡觉,不浪费 CPU!

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

pthread_mutex_t mutex;
pthread_cond_t cond;

int flag = 0; // 0:A打印  1:B打印

void *print_A(void *arg)
{
    while (1) {
        pthread_mutex_lock(&mutex);

        // 条件不满足就阻塞等待(不占CPU)
        while (flag != 0)
            pthread_cond_wait(&cond, &mutex);

        printf("A\n");
        flag = 1;
        sleep(1);

        // 唤醒B
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }
}

void *print_B(void *arg)
{
    while (1) {
        pthread_mutex_lock(&mutex);

        while (flag != 1)
            pthread_cond_wait(&cond, &mutex);

        printf("B\n");
        flag = 0;
        sleep(1);

        // 唤醒A
        pthread_cond_signal(&cond);
        pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    pthread_t tid1, tid2;

    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);

    pthread_create(&tid1, NULL, print_A, NULL);
    pthread_create(&tid2, NULL, print_B, NULL);

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);

    return 0;
}
相关推荐
zzzsde2 小时前
【Linux】库的制作和使用(3)ELF&&动态链接
linux·运维·服务器
CQU_JIAKE2 小时前
4.3【A]
linux·运维·服务器
qing222222222 小时前
Linux中修改mysql数据表
linux·运维·mysql
Alvin千里无风2 小时前
在 Ubuntu 上从源码安装 Nanobot:轻量级 AI 助手完整指南
linux·人工智能·ubuntu
杨云龙UP2 小时前
Oracle 中 NOMOUNT、MOUNT、OPEN 怎么理解? 在不同场景下如何操作?_20260402
linux·运维·数据库·oracle
Amctwd3 小时前
【Linux】OpenCode 安装教程
linux·运维·服务器
wwj888wwj3 小时前
Docker基础(复习)
java·linux·运维·docker
paldier3 小时前
rootfs挂载失败(error -5)的一个可能
linux
2401_892070984 小时前
【Linux C++ 日志系统实战】日志消息对象 LogMessage 完整实现:流式拼装 + 标准化输出
linux·c++·日志系统·流式日志