文章目录
- 0. 引言
- 1. 什么是线程优先级反转?
- 2. 不正确的示例代码
- 关键点总结
- 运行时分析
- 3. 优先级反转的影响
- 系统延迟和性能问题:
- 崩溃或死锁:
- 4. 如何避免线程优先级反转?
- 4.1. 分离关键任务和非关键任务
- 4.2. 高优先级采用非阻塞方式获取锁
- 4.3. 优先级继承机制(Priority Inheritance)
0. 引言
在多线程编程中,线程之间的资源同步如果处理不当,会出现**线程优先级反转(Priority Inversion)**的情况,这可能导致系统性能下降,甚至引发崩溃。
本文将通过 C++ 示例代码解释优先级反转的概念,并提出避免这一问题的方法。
1. 什么是线程优先级反转?
线程优先级反转通常发生在以下场景中:
- 高优先级线程(Task A)需要访问一个资源,而这个资源当前被低优先级线程(Task C)占用。
- 中优先级线程(Task B)在高优先级线程与低优先级线程之间运行,并且它的优先级高于低优先级线程但低于高优先级线程。
- 当高优先级线程(Task A)请求访问该资源时,由于该资源被低优先级线程(Task C)持有,高优先级线程会被阻塞。
- 这时,中优先级线程(Task B)会抢占 CPU 资源,并先于高优先级线程执行。更糟糕的是,低优先级线程(Task C)可能由于中优先级线程(Task B)的干扰,无法及时释放资源。
最终,系统可能表现为高优先级线程被延迟执行,低优先级线程却阻塞了高优先级线程的执行,导致性能不稳定或系统崩溃。
2. 不正确的示例代码
如下通过将 lowPriorityTask
中的锁放入 std::lock_guard
里并确保它在资源占用期间持有资源锁,模拟了一个真实的优先级反转问题:低优先级线程占用资源后,高优先级线程被迫等待,直到低优先级线程释放资源。
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
// 定义一个全局资源锁
std::mutex resourceMutex;
// 低优先级线程,模拟长时间占用资源
void lowPriorityTask() {
std::lock_guard<std::mutex> lock(resourceMutex); // 低优先级线程持有资源锁
std::cout << "Low priority task started, holding resource...\n";
std::this_thread::sleep_for(std::chrono::seconds(3)); // 模拟资源占用
std::cout << "Low priority task finished, releasing resource.\n";
}
// 中优先级线程,模拟执行某些任务
void mediumPriorityTask() {
std::cout << "Medium priority task started.\n";
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟任务
std::cout << "Medium priority task finished.\n";
}
// 高优先级线程,模拟尝试获取资源
void highPriorityTask() {
std::cout << "High priority task trying to acquire resource...\n";
std::lock_guard<std::mutex> lock(resourceMutex); // 需要资源锁
std::cout << "High priority task acquired resource and started working.\n";
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟任务
std::cout << "High priority task finished.\n";
}
int main() {
// 创建低优先级线程,中优先级线程和高优先级线程
std::thread lowTask(lowPriorityTask); // 低优先级线程
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 等待低优先级线程获取资源
std::thread mediumTask(mediumPriorityTask); // 中优先级线程
std::thread highTask(highPriorityTask); // 高优先级线程
// 等待线程完成
lowTask.join();
mediumTask.join();
highTask.join();
return 0;
}
关键点总结
-
低优先级线程 (
lowPriorityTask
):在执行时获取resourceMutex
锁,并模拟资源占用 3 秒。 -
高优先级线程 (
highPriorityTask
):尝试在lowPriorityTask
持有资源时获取锁,由于低优先级线程已经占用资源,高优先级线程将会被阻塞,直到低优先级线程释放锁。 -
中优先级线程 (
mediumPriorityTask
):在低优先级线程占用资源时执行。由于中优先级线程的优先级比低优先级线程高,它会在低优先级线程执行时被调度执行,并不会阻塞。
运行时分析
- 低优先级线程 开始执行并锁定资源。
- 中优先级线程 开始执行,并占用 CPU,尽管它的优先级低于高优先级线程,但它会先于高优先级线程执行。
- 高优先级线程 尝试获得资源并被阻塞,直到低优先级线程释放锁。
输出如下:
Low priority task started, holding resource...
Medium priority task started.
Medium priority task finished.
High priority task trying to acquire resource...
Low priority task finished, releasing resource.
High priority task acquired resource and started working.
High priority task finished.
3. 优先级反转的影响
系统延迟和性能问题:
当优先级反转发生时,系统中的高优先级任务被延迟,可能导致系统响应变慢,影响实时性要求较高的应用,如嵌入式系统、实时操作系统等。
崩溃或死锁:
在某些情况下,优先级反转可能引发更严重的问题。高优先级线程的延迟可能导致系统错过了某些关键的时机,甚至在多线程程序中引发死锁或崩溃。
4. 如何避免线程优先级反转?
4.1. 分离关键任务和非关键任务
原理:
将系统的关键任务和非关键任务分离,避免它们之间的资源竞争,从而减少优先级反转的机会。
实现方法:
- 使用进程间通信(IPC): 将关键任务和非关键任务放在不同的进程中,通过消息队列、管道等方式通信。
- 资源隔离: 为不同优先级的任务分配独立的资源,避免共享。
4.2. 高优先级采用非阻塞方式获取锁
原理:
使用非阻塞方式获取锁,避免线程因为等待资源而被长时间阻塞。
实现方法:
-
尝试获取锁失败时立即返回:
if (pthread_mutex_trylock(&mutex) == 0) { // 获取锁成功 // 访问共享资源 pthread_mutex_unlock(&mutex); } else { // 获取锁失败,执行其他操作 }
-
设置锁的超时时间:
struct timespec timeout; clock_gettime(CLOCK_REALTIME, &timeout); timeout.tv_sec += 1; // 设置1秒超时 if (pthread_mutex_timedlock(&mutex, &timeout) == 0) { // 获取锁成功 // 访问共享资源 pthread_mutex_unlock(&mutex); } else { // 获取锁超时,执行其他操作 }
为了避免线程优先级反转,
4.3. 优先级继承机制(Priority Inheritance)
原理:
优先级继承机制是在低优先级线程占有共享资源而高优先级线程等待该资源时,临时将低优先级线程的优先级提升到与等待线程相同的级别。这样,低优先级线程可以更快地完成任务并释放资源,减少高优先级线程的等待时间。一旦低优先级线程释放了资源,它的优先级就会恢复到原来的水平。
注意,并不是所有的Linux内核都支持该优先级继承机制。一般而言,使用内核PREEMPT-RT是支持优先级继承的。其他内核版本也可能支持,需要确保内核配置中启用了相应的选项(如CONFIG_RT_MUTEXES)。
改进后代码:
#include <pthread.h>
#include <iostream>
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
void initMutexWithPriorityInheritance() {
if (pthread_mutexattr_init(&attr) != 0) {
std::cerr << "Failed to initialize mutex attributes\n";
exit(EXIT_FAILURE);
}
if (pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT) != 0) {
std::cerr << "Failed to set priority inheritance protocol\n";
pthread_mutexattr_destroy(&attr);
exit(EXIT_FAILURE);
}
if (pthread_mutex_init(&mutex, &attr) != 0) {
std::cerr << "Failed to initialize mutex\n";
pthread_mutexattr_destroy(&attr);
exit(EXIT_FAILURE);
}
pthread_mutexattr_destroy(&attr); // 清理属性对象
}
void lowPriorityTask() {
if (pthread_mutex_lock(&mutex) != 0) {
std::cerr << "Failed to lock mutex in low priority task\n";
return;
}
std::cout << "Low priority task: Resource acquired, performing task...\n";
std::this_thread::sleep_for(std::chrono::seconds(3)); // 模拟任务
if (pthread_mutex_unlock(&mutex) != 0) {
std::cerr << "Failed to unlock mutex in low priority task\n";
}
std::cout << "Low priority task: Resource released\n";
}
void highPriorityTask() {
if (pthread_mutex_lock(&mutex) != 0) {
std::cerr << "Failed to lock mutex in high priority task\n";
return;
}
std::cout << "High priority task: Resource acquired, performing task...\n";
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟任务
if (pthread_mutex_unlock(&mutex) != 0) {
std::cerr << "Failed to unlock mutex in high priority task\n";
}
std::cout << "High priority task: Resource released\n";
}
int main() {
initMutexWithPriorityInheritance();
std::thread lowTask(lowPriorityTask); // 低优先级线程
std::this_thread::sleep_for(std::chrono::milliseconds(500)); // 等待低优先级线程获取资源
std::thread highTask(highPriorityTask); // 高优先级线程
// 等待线程完成
lowTask.join();
highTask.join();
pthread_mutex_destroy(&mutex); // 清理互斥锁
return 0;
}