C++ multithreading-thread scheduling API

Foreword

When C++ multi-threaded programming, threads are usually created directly to start executing tasks without setting priorities. However, in some special scenarios, it may be necessary to set different priorities for incompetent thread tasks so that threads can be processed first. For high-level tasks, the pthread library provides some APIs to set the priority of threads.

Thread Scheduling

In pthread, the following thread scheduling mechanism is defined

#define SCHED_OTHER 0
#define SCHED_FIFO 1
#define SCHED_RR 2
#ifdef __USE_GNU
# define SCHED_BATCH 3
# define SCHED_ISO 4
#define SCHED_IDLE 5
#define SCHED_DEADLINE 6

# define SCHED_RESET_ON_FORK 0x40000000
#endif

The above scheduling mechanism can be divided into ordinary scheduling mechanism and real-time scheduling mechanism. In the ordinary scheduling mechanism, there is no concept of priority, and its priority is 0, so it has The threads of the real-time scheduling mechanism will directly preempt the threads of the ordinary scheduling mechanism. So today we mainly discuss the real-time scheduling mechanism.

For real-time scheduling policies (SCHED_FIFO, SCHED_RR), the priority needs to be set to a value from 1 (minimum) to 99 (maximum);
The scheduler maintains a list of threads to be scheduled for each priority. When scheduling is required, the scheduler accesses the non-empty list of the highest priority, and then selects a thread from the head of the list to schedule and run;

The thread’s scheduling policy determines where in which list a schedulable thread should be placed;

All scheduling supports preemption. If a high-priority thread is ready to run, it will preempt the currently running thread, which causes the current thread to be re-added to the linked list waiting for scheduling. ;The scheduling policy determines the order of schedulable threads in the same priority list;

SCHED_FIFO

SCHED_FIFO is first-in-first-out scheduling. It is a real-time scheduling strategy with the concept of priority. It will definitely preempt ordinary scheduling strategy threads. There is no concept of time slice algorithm inside it.
First-in-first-out scheduling has the following rules:

  1. After a SCHED_FIFO thread is preempted by a high-priority thread, it will be added to the head of its priority waiting list.
  2. When a blocked SCHED_FIFO thread is programmed from the blocked state to the runnable state, it will be added to the head of its priority list.
  3. When a thread’s priority changes, the following corresponding situations will occur:
  • Priority increased: it will be added to the tail of the new priority waiting list
  • The priority has not changed: its position in the priority waiting list is not indicated
  • The priority becomes lower: it will be added to the head of the new priority waiting list

According to POSIX regulations, except using pthread_setschedprio(3), other methods of changing the scheduling policy or priority will add the corresponding thread to the end of the corresponding priority waiting list.

4. If a thread uses sched_yield(2), it will not be added to the end of the current waiting list.
SCHED_FIFO will continue to run until it is blocked by an IO request, or is preempted by a higher priority thread, or sched_yield is called itself.

Test Case

int attach_cpu(int cpu_index) {<!-- -->
  int cpu_num = sysconf(_SC_NPROCESSORS_CONF);
  if (cpu_index < 0 || cpu_index >= cpu_num) {<!-- -->
    printf("cpu index ERROR!\
");
    return -1;
  }

  cpu_set_t mask;
  CPU_ZERO( & amp;mask);
  CPU_SET(cpu_index, & amp;mask);

  if (pthread_setaffinity_np(pthread_self(), sizeof(mask), & amp;mask) < 0) {<!-- -->
    printf("set affinity np ERROR!\
");
    return -1;
  }

  return 0;
}

long long a = 0;
long long b = 0;

void *th1(void *para) {<!-- -->
  attach_cpu(0);
  for (long long int i = 0; i < 10000000000; i + + ) {<!-- -->
    a + + ;
    printf("a=%lld\
", a);
  }
  return nullptr;
}

void *th2(void *para) {<!-- -->
  attach_cpu(0);
  for (long long int i = 0; i < 10000000000; i + + ) {<!-- -->
    b + + ;
    printf("b=%lld\
", b);
  }
  return nullptr;
}

int main() {<!-- -->
  int minlevel = sched_get_priority_min(SCHED_FIFO);
  int maxlevel = sched_get_priority_max(SCHED_FIFO);
  if (minlevel == -1 || maxlevel == -1) {<!-- -->
    printf("error\
");
    return -1;
  }
  printf("minlevel = %d, maxlevel = %d\
", minlevel, maxlevel);
  pthread_t t1;
  pthread_create( & amp;t1, nullptr, th1, nullptr);
  struct sched_param para;
  para.sched_priority = minlevel;
  pthread_setschedparam(t1, SCHED_FIFO, & amp;para);

  pthread_t t2;
  pthread_create( & amp;t2, nullptr, th2, nullptr);
  struct sched_param para2;
  para.sched_priority = maxlevel;
  pthread_setschedparam(t2, SCHED_FIFO, & amp;para2);

  while (1) {<!-- -->
    // printf("a=%lld, b=%lld\
", a, b);
    sleep(1);
  }

  pthread_join(t1, nullptr);
  pthread_join(t2, nullptr);
  return 0;
}

Run this code. When th2 is started, it will seize the cpu where th1 is located and keep occupying it until the execution ends.

SCHED_RR

SCHED_RR is the round robin scheduling strategy, which is roughly the same as SCHED_FIFO. The difference is that SCHED_RR limits the maximum time a thread can occupy the cpu. When the current thread occupies the cpu for more than the time slice , will add the current thread to the end of the current waiting queue, and then run the next thread. If the current thread is preempted by other high-priority threads before the time slice is used up, the next time the thread continues to run, it will only run for the remaining CPU time.

You can get the time slice of the thread through the function sched_rr_get_interval.

Test Case

int main(){<!-- -->

int minlevel = sched_get_priority_min(SCHED_RR);
  int maxlevel = sched_get_priority_max(SCHED_RR);
  if (minlevel == -1 || maxlevel == -1) {<!-- -->
    printf("error\
");
    return;
  }
  printf("minlevel = %d, maxlevel = %d\
", minlevel, maxlevel);
  pthread_t t1;
  pthread_create( & amp;t1, nullptr, th1, nullptr);
  struct sched_param para;
  para.sched_priority = minlevel;
  pthread_setschedparam(t1, SCHED_RR, & amp;para);

  // struct timespec t;

  // if (sched_rr_get_interval(getpid(), & amp;t) == -1) {<!-- -->
  // printf("get interval failed\
");
  // return;
  // }
  // printf("t.sec = %ld.%ld\
", t.tv_sec, t.tv_nsec / 1000);

  pthread_t t2;
  pthread_create( & amp;t2, nullptr, th2, nullptr);
  struct sched_param para2;
  para.sched_priority = maxlevel;
  pthread_setschedparam(t2, SCHED_RR, & amp;para2);

  while (1) {<!-- -->
    // printf("a=%lld, b=%lld\
", a, b);
    sleep(1);
  }

  pthread_join(t1, nullptr);
  pthread_join(t2, nullptr);
}

When outputting, a is output first. When b starts, since b has a higher priority than a, the cpu is occupied by b. However, after b runs for a cpu time slice, the system assigns the cpu to a. Then a starts outputting.

SCHED_OTHER

SCHED_OTHER can only be used for threads with priority 0. The SCHED_OTHER strategy is a unified standard strategy for all threads that do not require real-time scheduling. The scheduler determines which SCHED_OTHER thread to call through dynamic priority. The dynamic priority is Based on the nice value, the nice value increases with the total amount of time waiting to run but not scheduled for execution; this mechanism ensures the fairness of all SCHED_OTHER thread scheduling;

Summary

Response time: SCHED_RR can ensure that the system’s response time for each request is equal for I/O-intensive processes; while SCHED_FIFO can ensure that the system’s response time is equal for CPU-intensive processes. , which can make them run for a longer time and reduce the cost of process context switching.

Therefore, when choosing to use SCHED_RR or SCHED_FIFO, you need to consider the specific situation. If there are strict requirements on response time and the execution time of the process is relatively short, the SCHED_RR strategy is suitable; if the response time requirements of the process are not so strict and more emphasis is placed on the execution order of the process, the SCHED_FIFO strategy is suitable.

"C++ Primer" and "Effective C++" are essential books for C++ developers. If you want to get started with C++, and want to To improve C++ development technology, these two books can be said to be must-haves. In addition, "Linux High-Performance Server Programming" and "Linux Multi-threaded Server Programming: Using muduo C++ Network Library". (Chen Shuo)" are secrets to quickly improve your Linux development capabilities. "Dahua Design Pattern" can enhance our model extraction and design capabilities and write more elegant code. At the same time, “Introduction to Operating Systems” is a must-read for development. It also takes some effort to search for relevant resources on the Internet. Students who need it can follow gzh [Programmer DeRozan] and reply [1207 】Get it quickly and for free~