10个线程依次打印0-100
系统线程调度策略有三种,默认为SCHED_OTHER
:
/*
* POSIX scheduling policies
*/
#define SCHED_OTHER 1
#define SCHED_FIFO 4
#define SCHED_RR 2
方法一
线程竞争互斥锁。如果某个线程抢到锁且轮到此线程输出,则输出;否则直接释放锁。
#include <iostream>
#include <thread>
int number = 0;
int notPrinted = 0;
std::mutex mutex_number;
constexpr int MAX_NUM = 100;
void printRemainder(const int remainder) {
int policy;
sched_param sch{};
pthread_getschedparam(pthread_self(), &policy, &sch);
printf("sched policy %d, thread priority %d\n", policy, sch.sched_priority);
while (true) {
mutex_number.lock();
if (number > MAX_NUM) {
mutex_number.unlock();
break;
}
if (number % 10 == remainder) {
std::cout << "mythread_" << remainder << ": " << number << std::endl; // 输出
number++;
} else {
notPrinted++;
}
mutex_number.unlock();
}
std::cout << "mythread_" << remainder << " finish" << std::endl; // mythread_1完成
}
int main() {
std::cout << "Create and Start!" << std::endl;
std::vector<std::thread *> threads;
threads.reserve(9);
sched_param sch{};
for (int i = 0; i < 10; ++i) {
auto *thread = new std::thread(printRemainder, i);
pthread_setschedparam(thread->native_handle(), SCHED_OTHER, &sch);
threads.emplace_back(thread);
}
// 调用每个thread的join
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));
std::cout << "Not printed: " << notPrinted << std::endl;
std::cout << "Finish and Exit!" << std::endl;
return 0;
}
可以看到最终输出了Not printed: 144525
,也就是说有144525次线程调度并未输出数值,因为系统调度到的线程不是接下来需要输出数字的线程。
这种方法浪费了太多的系统资源。
如果在线程释放锁之后主动放弃CPU(sched_yield();
),Not printed
会降低至900
左右。(Why?)
方法二
因为线程调度是由操作系统完成的,所以方法一不能很好的完成线程0打印0之后唤醒线程1去打印1这种操作。我们需要尝试修改操作系统的线程调度策略来完成线程的顺序调度。
我们将调度策略设置为SCHED_FIFO
:
#include <iostream>
#include <thread>
int number = 0;
int notPrinted = 0;
std::mutex mutex_number;
constexpr int MAX_NUM = 100;
void printRemainder(int const remainder) {
int policy;
sched_param sch{};
pthread_getschedparam(pthread_self(), &policy, &sch);
printf("sched policy %d, thread priority %d\n", policy, sch.sched_priority);
while (true) {
mutex_number.lock();
if (number > MAX_NUM) {
mutex_number.unlock();
break;
}
if (number % 10 == remainder) {
std::cout << "mythread_" << remainder << ": " << number << std::endl; // 输出
number++;
} else {
notPrinted++;
}
mutex_number.unlock();
// 主动放弃CPU
sched_yield();
}
std::cout << "mythread_" << remainder << " finish" << std::endl; // mythread_1完成
}
int main() {
std::cout << "Create and Start!" << std::endl;
std::vector<std::thread *> threads;
threads.reserve(9);
sched_param sch{};
for (int i = 0; i < 10; ++i) {
auto *thread = new std::thread(printRemainder, i);
pthread_setschedparam(thread->native_handle(), SCHED_FIFO, &sch);
threads.emplace_back(thread);
}
// 调用每个thread的join
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));
std::cout << "Not printed: " << notPrinted << std::endl;
std::cout << "Finish and Exit!" << std::endl;
return 0;
}
可以看到最终输出了Not printed: 1872
,相比于方法一的144525已经有很大的优化。这1872次调度到了错误的线程,是因为线程在创建完成后立刻开始进入调度队列,被系统调度时开始执行。当我们创建了2个线程时,系统不停地调度这两个线程,但实际上真正需要输出数字的线程3还未被创建,所以线程创建初期时会存在调度了错误的线程的情况。
方法三
针对方法二再进行优化,当线程创建时,并不真正开始执行,而是所有线程创建完成后统一开始执行。只需在线程创建前做一下mutex_number.lock();
,线程全部创建完成后执行mutex_number.unlock();
来解锁,让所有线程同时开始。
#include <iostream>
#include <thread>
int number = 0;
int notPrinted = 0;
std::mutex mutex_number;
constexpr int MAX_NUM = 100;
void printRemainder(int const remainder) {
int policy;
sched_param sch{};
pthread_getschedparam(pthread_self(), &policy, &sch);
printf("sched policy %d, thread priority %d\n", policy, sch.sched_priority);
while (true) {
mutex_number.lock();
if (number > MAX_NUM) {
mutex_number.unlock();
break;
}
if (number % 10 == remainder) {
std::cout << "mythread_" << remainder << ": " << number << std::endl; // 输出
number++;
} else {
notPrinted++;
}
mutex_number.unlock();
// 主动放弃CPU
sched_yield();
}
std::cout << "mythread_" << remainder << " finish" << std::endl; // mythread_1完成
}
// 我希望thread创建之后不要立刻运行,而是等10个线程创建完成后统一开始执行
int main() {
std::cout << "Create and Start!" << std::endl;
std::vector<std::thread *> threads;
threads.reserve(9);
sched_param sch{};
mutex_number.lock();
for (int i = 0; i < 10; ++i) {
auto *thread = new std::thread(printRemainder, i);
pthread_setschedparam(thread->native_handle(), SCHED_FIFO, &sch);
threads.emplace_back(thread);
}
mutex_number.unlock();
// 调用每个thread的join
std::for_each(threads.begin(), threads.end(), std::mem_fn(&std::thread::join));
std::cout << "Not printed: " << notPrinted << std::endl;
std::cout << "Finish and Exit!" << std::endl;
return 0;
}
这种方式下依然输出Not printed: 1214
,检查发现线程调度并不是严格按照0、1、2、3...、9的顺序调度的,而是比较平均地调度了每个线程。我们使用10个线程输出0-100,每次调度到正确的线程的可能性是10%,那么正确调度100次所需要的平均调度总次数为1000次。在多次运行时,也发现Not printed
平均在900左右。
总结
SCHED_FIFO
调度策略并不是我们想的那样能够严格按照顺序来执行这10个线程,并且sched_yield();
也很大程度上影响了调度的效率,具体原因还需深究。
不主动释放CPU Not printed | 主动释放CPU Not printed | |
---|---|---|
方法一 | 144525 | 1899 |
方法二 | 59165 | 1872 |
方法三 | 66781 | 1895 |