目录
1.进程和线程的概念:
1.1 进程(Process):
1.2线程(Thread):
1.3 对比总结:
2.多线程编程:
2.1 基于线程的多任务处理(Thread):
2.1.1. mbed.h 中的线程类
2.2 注意事项:(死锁)
2.2.1. 进程死锁
2.2.2 线程死锁
2.2.3 避免死锁(Thread):
1.进程和线程的概念:
进程和线程是操作系统中两个重要的概念,它们之间存在着密切的关系,但有不同的特性和用途。
1.1 进程(Process):
- 操作系统中资源分配的最小单位,每个进程拥有独立的内存空间、系统资源(文件描述符、网络连接等)和生命周期。进程之间相对独立,无法直接共享彼此的内存空间。
进程虽然无法直接共享彼此内存空间 ,但是操作系统提供了一些机制,使得进程之间可以共享或交换部分资源,但这些共享和通信需要通过特殊的手段实现,而不是像线程那样直接访问共享的内存空间。
进程间资源共享的常用方式:
-
管道(Pipe):允许一个进程将数据写入管道,另一个进程从管道中读取,用于单向通信。
-
消息队列(Message Queue):操作系统提供的一种数据结构,用于在多个进程之间发送和接收消息,实现异步通信。
-
共享内存(Shared Memory):一种特殊的内存区域,多个进程可以映射到这个区域来进行数据共享。共享内存速度快,但需要借助信号量等同步机制,避免数据竞争。
-
信号量(Semaphore):用来控制对共享资源的访问,防止多个进程同时访问导致冲突。
-
套接字(Socket):尤其用于分布式系统中,通过网络通信实现跨进程的数据共享和传递。
1.2线程(Thread):
- 是操作系统中CPU调度的最小单位。线程共享同一进程的内存空间和系统资源,可以认为是进程中的“轻量级”执行单元。同一进程中的线程之间能够共享全局变量和数据,通讯成本低。
1.3 对比总结:
2.多线程编程:
多线程是多任务处理的一种特殊形式,多任务处理允许让电脑同时运行两个或两个以上的程序。在一般情况下,有两种类型的多任务处理:基于进程和基于线程。
-
基于进程的多任务处理:每个进程都有自己的内存空间和系统资源,进程之间相对独立,切换开销较大,但安全性高。每个进程可以运行不同的程序,相互之间的通信通常需要更复杂的机制,比如进程间通信(IPC)。
-
基于线程的多任务处理:线程是进程内的一个执行单元,多个线程共享同一进程的内存空间和资源,因此切换开销相对较小,适合需要频繁进行上下文切换的任务。由于共享内存,线程间的通信更为高效,但也带来了同步和安全性的问题。
2.1 基于线程的多任务处理(Thread):
以嵌入式单片为例,在mbed OS
中,mbed.h
库提供了对线程的支持,使得在嵌入式系统中可以方便地实现多线程。
2.1.1. mbed.h
中的线程类
在mbed.h
库中,线程主要通过 Thread
类来管理和操作。该类提供了创建、启动、暂停、恢复和终止线程的基本操作方法。
常用的 Thread
类方法有:
Thread::start(callback)
:启动线程,执行指定的回调函数。Thread::join()
:阻塞当前线程,直到其被调用线程完成。(顺序执行),调用join()
后,主线程会被阻塞,直到子线程执行完毕。Thread::terminate()
:强制终止线程的执行。Thread::yield()
:让出CPU,使其他线程有机会运行。Thread::sleep_for(milliseconds)
:使线程在指定的毫秒数内休眠。
#include "mbed.h"
// 定义LED灯
DigitalOut led1(LED1);
DigitalOut led2(LED2);
// 定义线程
Thread thread1;
Thread thread2;
// 线程函数1 - 控制LED1闪烁
void led1_thread() {
while (true) {
led1 = !led1; // 切换LED1的状态
ThisThread::sleep_for(500ms); // 每500ms切换一次
}
}
// 线程函数2 - 控制LED2闪烁
void led2_thread() {
while (true) {
led2 = !led2; // 切换LED2的状态
ThisThread::sleep_for(1000ms); // 每1000ms切换一次
}
}
int main() {
// 启动线程
thread1.start(led1_thread);
thread2.start(led2_thread);
// 主线程可以执行其他任务,或等待子线程结束
thread1.join();
thread2.join();
}
2.2 注意事项:(死锁)
进程死锁和线程死锁都涉及资源的相互等待,导致无法继续执行。
2.2.1. 进程死锁
进程死锁发生在两个或多个进程之间。当进程A持有资源1并等待资源2,而进程B持有资源2并等待资源1时,两个进程将永远处于等待状态,无法继续执行。
特征
-
互斥:资源只能被一个进程占用。(不同同时访问)
-
持有并等待:进程在持有至少一个资源的情况下,申请其他资源。
-
不剥夺:已经分配给进程的资源在其完成之前不能被强制剥夺。
-
循环等待:存在一种进程资源的循环等待关系。
示例
进程A持有资源R1,并请求资源R2。 进程B持有资源R2,并请求资源R1。
2.2.2 线程死锁
定义
在多线程程序中,线程死锁通常发生在多个线程尝试以不同的顺序获取多个锁时。例如,线程1持有锁A并等待锁B,而线程2持有锁B并等待锁A,这将导致两个线程永远处于等待状态。
特征
-
与进程死锁相似,同样具有互斥、持有并等待、不剥夺和循环等待的特征。
-
线程死锁通常更复杂,因为线程之间的锁依赖关系可能更难以追踪
2.2.3 避免死锁(Thread):
同步和锁
同步指的是在多个线程之间协调它们的执行顺序,以确保某些操作以特定的顺序完成,防止出现竞态条件(race condition)。同步的目的在于确保共享资源的安全访问。
方法:同步可以通过多种方式实现,包括:
- 互斥锁(Mutex):保证在某一时刻只有一个线程可以访问某个资源。
- 信号量(Semaphore):控制同时访问某个资源的线程数量。
- 条件变量(Condition Variable):使线程在某些条件不满足时阻塞,直到条件满足。
- 读写锁(Read-Write Lock):允许多个线程同时读数据,但在写数据时需要独占访问。
互斥锁(Mutex):
#include "mbed.h"
Mutex mutex; // 创建一个互斥锁
int sharedResource = 0; // 共享资源
void threadTask() {
for (int i = 0; i < 5; i++) {
mutex.lock(); // 加锁,开始访问共享资源
sharedResource++; // 修改共享资源
printf("线程 %d 修改共享资源: %d\n", ThisThread::get_id(), sharedResource);
mutex.unlock(); // 解锁,释放共享资源
ThisThread::sleep_for(500ms);
}
}
int main() {
Thread thread1;
Thread thread2;
thread1.start(threadTask);
thread2.start(threadTask);
thread1.join();
thread2.join();
printf("最终共享资源值: %d\n", sharedResource);
return 0;
}
- 互斥锁(
mutex
)确保在同一时刻只有一个线程可以访问和修改sharedResource
,避免了数据竞争。 - 每个线程在修改共享资源前调用
mutex.lock()
,完成后调用mutex.unlock()
,以确保资源安全。
读写锁(Read-Write Lock):
#include "mbed.h"
RWLock rwLock; // 创建读写锁
int sharedData = 0; // 共享数据
// 读线程
void readTask() {
for (int i = 0; i < 5; i++) {
rwLock.lock_read(); // 获取读锁
printf("读线程 %d 读取共享数据: %d\n", ThisThread::get_id(), sharedData);
rwLock.unlock_read(); // 释放读锁
ThisThread::sleep_for(500ms);
}
}
// 写线程
void writeTask() {
for (int i = 0; i < 3; i++) {
rwLock.lock_write(); // 获取写锁
sharedData++; // 更新共享数据
printf("写线程 %d 更新共享数据: %d\n", ThisThread::get_id(), sharedData);
rwLock.unlock_write(); // 释放写锁
ThisThread::sleep_for(1000ms);
}
}
int main() {
Thread readThreads[5];
Thread writeThread;
// 启动多个读线程
for (int i = 0; i < 5; i++) {
readThreads[i].start(readTask);
}
// 启动一个写线程
writeThread.start(writeTask);
// 等待所有线程完成
for (int i = 0; i < 5; i++) {
readThreads[i].join();
}
writeThread.join();
printf("所有任务完成\n");
return 0;
}
-
多个读线程(
readTask
)同时读取共享数据sharedData
。由于使用了读写锁,多个读线程可以并行地访问共享数据。 -
写线程(
writeTask
)在更新共享数据时,必须获取写锁,这会阻止其他线程(无论是读线程还是写线程)访问共享数据,直到写操作完成。