本文共1413字。
Copyright: 知识共享署名 非商业性使用 相同方式共享 4.0 国际许可协议
|
CC BY-NC-SA 4.0 系统线程调度策略有三种,默认为SCHED_OTHER
:
1 2 3 4 5 6 #define SCHED_OTHER 1 #define SCHED_FIFO 4 #define SCHED_RR 2
方法一 线程竞争互斥锁。如果某个线程抢到锁且轮到此线程输出,则输出;否则直接释放锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 #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; } 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); } 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
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #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 (); sched_yield (); } std::cout << "mythread_" << remainder << " finish" << std::endl; } 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); } 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();
来解锁,让所有线程同时开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 #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 (); sched_yield (); } std::cout << "mythread_" << remainder << " finish" << std::endl; } 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 (); 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