Linux 物联网网关主控系统-Linux主控部分(四)
- 一、引入问题
- 二、线程基础
-
- 1.线程的核心定义
- [2.进程与线程的调度 / 资源分配重构](#2.进程与线程的调度 / 资源分配重构)
- 3.主线程与多线程进程
- 4.线程的基本特性
- 5、多线程与多进程的形象类比
- [6.线程的资源特性(核心:共享 + 私有)](#6.线程的资源特性(核心:共享 + 私有))
- 7.多线程的核心优点
- 三、多线程编程-线程创建
- [四、多线程 ------ 互斥锁](#四、多线程 —— 互斥锁)
- 五、多线程-条件变量
一、引入问题
问题 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;
}