Skip to content

10个线程依次打印0-100

系统线程调度策略有三种,默认为SCHED_OTHER

cpp
/*
 * POSIX scheduling policies
 */
#define SCHED_OTHER                1
#define SCHED_FIFO                 4
#define SCHED_RR                   2

方法一

线程竞争互斥锁。如果某个线程抢到锁且轮到此线程输出,则输出;否则直接释放锁。

cpp
#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

cpp
#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();来解锁,让所有线程同时开始。

cpp
#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
方法一1445251899
方法二591651872
方法三667811895