Scheduling native threads at Android NDK

This recipe discusses how to schedule native threads at Android NDK.

Getting ready...

Readers are suggested to read the Manipulating classes in JNI and Calling static and instance methods from native code recipes in Chapter 2, Java Native Interface, and Creating and terminating native threads at Android NDK recipe in this chapter.

How to do it...

The following steps will help us create an Android project that demonstrates threads scheduling at Android NDK:

  1. Create an Android application named NativeThreadsSchedule. Set the package name as cookbook.chapter6.nativethreadsschedule. Refer to the Loading native libraries and registering native methods recipe in Chapter 2, Java Native Interface for more detailed instructions.
  2. Right-click on the project NativeThreadsSchedule, select Android Tools | Add Native Support.
  3. Add a Java file named MainActivity.java under package cookbook.chapter6.nativethreadsschedule. This Java file simply loads the native library NativeThreadsSchedule and calls the native methods.
  4. Add five files named mylog.h, NativeThreadsSchedule.h, NativeThreadsSchedule.cpp, SetPriority.cpp, and JNIProcessSetThreadPriority.cpp under the jni folder. A part of the code in the last three files is shown as follows:
    • The NativeThreadsSchedule.cpp file contains the source code to demonstrate the threads scheduling functions defined in pthread.h

      jni_thread_scope demonstrates how to set the native thread contention scope:

      void jni_thread_scope() {
        pthread_attr_t attr;
        int ret;
        pid_t fpId = fork();
        if (0 == fpId) {
          pthread_attr_init(&attr);
          int ret = pthread_attr_setscope(&attr, PTHREAD_SCOPE_PROCESS);
          pthread_t thFive[5];
          int threadNums[5];
    int i;
          for (i = 0; i < 5; ++i) {
            threadNums[i] = i;      ret = pthread_create(&thFive[i], &attr, run_by_thread, (void*)&(threadNums[i]));
          }
          for (i = 0; i < 5; ++i) {
            ret = pthread_join(thFive[i], NULL);
          }
        } else {
          pthread_attr_init(&attr);
          int ret = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);
          pthread_t th1;
          int threadNum1 = 0;
          ret = pthread_create(&th1, &attr, run_by_thread, (void*)&threadNum1);
          ret = pthread_join(th1, NULL);
        }
        //code executed by both processes
        pthread_attr_destroy(&attr);
      }

      jni_thread_fifo demonstrates how to set the native thread scheduling policy and priority:

      void jni_thread_fifo() {
        pthread_attr_t attr;
        int ret;
        pid_t fpId = fork();
        struct sched_param prio;
        if (0 == fpId) {
          //the child process
          pthread_attr_init(&attr);
          pthread_t thFive[5];
          int threadNums[5];
          int i;
          for (i = 0; i < 5; ++i) {
            if (i == 4) {
              prio.sched_priority = 10;
            } else {
              prio.sched_priority = 1;
            }
            ret = pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
            ret = pthread_attr_setschedparam(&attr, &prio);
            threadNums[i] = i;
            ret = pthread_create(&thFive[i], &attr, run_by_thread, (void*)&(threadNums[i]));
            pthread_attr_t lattr;
            struct sched_param lprio;
            int lpolicy;
            pthread_getattr_np(thFive[i], &lattr);
            pthread_attr_getschedpolicy(&lattr, &lpolicy);
            pthread_attr_getschedparam(&lattr, &lprio);
            pthread_attr_destroy(&lattr);
          }
          for (i = 0; i < 5; ++i) {
            ret = pthread_join(thFive[i], NULL);
          }
        } else {
          //the parent process
          pthread_attr_init(&attr);
          prio.sched_priority = 10;
          ret = pthread_attr_setschedpolicy(&attr, SCHED_FIFO);
          ret = pthread_attr_setschedparam(&attr, &prio);
          pthread_t th1;
          int threadNum1 = 0;
          ret = pthread_create(&th1, &attr, run_by_thread, (void*)&threadNum1);
          pthread_attr_t lattr;
          struct sched_param lprio;
          int lpolicy;
          pthread_getattr_np(th1, &lattr);
          pthread_attr_getschedpolicy(&lattr, &lpolicy);
          pthread_attr_getschedparam(&lattr, &lprio);
          pthread_attr_destroy(&lattr);
          ret = pthread_join(th1, NULL);
        }
        //code executed by both processes
        pthread_attr_destroy(&attr);
      }

      run_by_thread is the actual function to be executed by each native thread:

      void *run_by_thread(void *arg) {
        int cnt = 18000000, i;
        int* threadNum = (int*)arg;
        for (i = 1; i < cnt; ++i) {
          if (0 == i%6000000) {
            LOGI(1, "process %d thread %d: %d", getpid(), *threadNum, i);
          }
        }
        LOGI(1, "process %d thread %d return", getpid(), *threadNum);
      }
    • The SetPriority.cpp file contains the source code to configure thread nice value through setpriority

      The jni_thread_set_priority method creates and joins five native methods:

      void jni_thread_set_priority() {
        int ret;
        pthread_t thFive[5];
        int threadNums[5];
        int i;
        for (i = 0; i < 5; ++i) {
          threadNums[i] = i;
          ret = pthread_create(&thFive[i], NULL, run_by_thread2, (void*)&(threadNums[i]));
        }
        for (i = 0; i < 5; ++i) {
          ret = pthread_join(thFive[i], NULL);
        }
      }

      The run_by_thread2 function is executed by each native thread:

      void *run_by_thread2(void *arg) {
        int cnt = 18000000, i;
        int* threadNum = (int*)arg;
        switch (*threadNum) {
        case 0:
          setpriority(PRIO_PROCESS, 0, 21);
          break;
        case 1:
          setpriority(PRIO_PROCESS, 0, 10);
          break;
        case 2:
          setpriority(PRIO_PROCESS, 0, 0);
          break;
        case 3:
          setpriority(PRIO_PROCESS, 0, -10);
          break;
        case 4:
          setpriority(PRIO_PROCESS, 0, -21);
          break;
        default:
          break;
        }
        for (i = 1; i < cnt; ++i) {
          if (0 == i%6000000) {
            int prio = getpriority(PRIO_PROCESS, 0);
            LOGI(1, "thread %d (prio = %d): %d", *threadNum, prio, i);
          }
        }
        int prio = getpriority(PRIO_PROCESS, 0);
        LOGI(1, "thread %d (prio = %d): %d return", *threadNum, prio, i);
      }
    • The JNIProcessSetThreadPriority.cpp file contains the source code to configure thread nice value through the android.os.Process.setThreadPriority Java method

      The jni_process_setThreadPriority method creates and joins five native threads:

      void jni_process_setThreadPriority() {
        int ret;
        pthread_t thFive[5];
        int threadNums[5];
        int i;
        for (i = 0; i < 5; ++i) {
          threadNums[i] = i;
          ret = pthread_create(&thFive[i], NULL, run_by_thread3, (void*)&(threadNums[i]));
          if(ret) {
            LOGE(1, "cannot create the thread %d: %d", i, ret);
          }
          LOGI(1, "thread %d started", i);
        }
        for (i = 0; i < 5; ++i) {
          ret = pthread_join(thFive[i], NULL);
          LOGI(1, "join returned for thread %d", i);
        }
      }

      The run_by_thread3 function is executed by each native thread. The thread nice value is set here:

      void *run_by_thread3(void *arg) {
        int cnt = 18000000, i;
        int* threadNum = (int*)arg;
        JNIEnv *env;
        jmethodID setThreadPriorityMID;
        cachedJvm->AttachCurrentThread(&env, NULL);
        jclass procClass = env->FindClass("android/os/Process");
        setThreadPriorityMID = env->GetStaticMethodID(procClass, "setThreadPriority", "(I)V");
        switch (*threadNum) {
        case 0:
          env->CallStaticVoidMethod(procClass, setThreadPriorityMID, 21);
          break;
        case 1:
          env->CallStaticVoidMethod(procClass, setThreadPriorityMID, 10);
          break;
        case 2:
          env->CallStaticVoidMethod(procClass, setThreadPriorityMID, 0);
          break;
        case 3:
          env->CallStaticVoidMethod(procClass, setThreadPriorityMID, -10);
          break;
        case 4:
          env->CallStaticVoidMethod(procClass, setThreadPriorityMID, -21);
          break;
        default:
          break;
      
       }
        //we can also use getThreadPriority(int tid) through JNI interface
        for (i = 1; i < cnt; ++i) {
          if (0 == i%6000000) {
            int prio = getpriority(PRIO_PROCESS, 0);
            LOGI(1, "thread %d (prio = %d): %d", *threadNum, prio, i);
          }
        }
        int prio = getpriority(PRIO_PROCESS, 0);
        LOGI(1, "thread %d (prio = %d): %d return", *threadNum, prio, i);
        cachedJvm->DetachCurrentThread();
      }
  5. Add an Android.mk file under the jni folder with the following content:
    LOCAL_PATH := $(call my-dir)
    include $(CLEAR_VARS)
    LOCAL_MODULE    := NativeThreadsSchedule
    LOCAL_SRC_FILES := NativeThreadsSchedule.cpp
    LOCAL_LDLIBS    := -llog
    include $(BUILD_SHARED_LIBRARY)
  6. In MainActivity.java, disable all native methods except jni_thread_scope. Build the project and run it. Start a terminal and enter the following command to monitor the logcat output:
    $ adb logcat -v time NativeThreadsSchedule:I *:S

    The following screenshot shows the output:

    How to do it...
  7. In MainActivity.java, disable all native methods except jni_thread_fifo. Build the project and run it. The logcat output is shown in the following screenshot:
    How to do it...
  8. In MainActivity.java, disable all native methods except jni_thread_set_priority. Build the project and run it. The logcat output is shown in the following screenshot:
    How to do it...
  9. In MainActivity.java, disable all native methods except jni_process_setThreadPriority. Build the project and run it. The logcat output is shown in the following screenshot:
    How to do it...

How it works...

We can schedule native threads by setting the scheduling contention scope, thread priority, and scheduling policy:

  • Scheduling contention scope: It determines the threads that a thread must compete against when the scheduler schedules threads
  • Thread priority: It determines which thread is more likely to be selected by the scheduler when a CPU is available
  • Scheduling policy: It determines how the scheduler schedules threads with the same priority

One way to adjust these settings is through the thread attribute. The following functions are defined in pthread.h to initialize and destroy an instance of pthread_attr_t:

int pthread_attr_init(pthread_attr_t * attr); 
int pthread_attr_destroy(pthread_attr_t * attr);

In these two functions, the input argument is a pointer to a pthread_attr_t object. We will now describe contention scope, thread priority, and scheduling policy in detail.

Scheduling contention scope

Two scopes are defined in a typical pthread implementation, namely PTHREAD_SCOPE_SYSTEM and PTHREAD_SCOPE_PROCESS. A system scope thread competes for the CPU with all other threads system-wide. On the other hand, a process scope thread is scheduled against other threads in the same process.

Android Bionic pthread.h defines the following two functions to set and get the thread scope:

int pthread_attr_setscope(pthread_attr_t *attr, int  scope); 
int pthread_attr_getscope(pthread_attr_t const *attr);

The two functions accept a pointer to a pthread attribute object as the input argument. The set function also includes a second argument to let us pass the scope constant. These two functions return a zero to indicate success and a nonzero value to signal failure.

It turns out pthread_attr_setscope with PTHREAD_SCOPE_PROCESS as second input argument is not supported by Android. In other words, Android native threads always have system scope. As shown in jni_thread_scope at NativeThreadsSchedule.cpp, calling pthread_attr_setscope with PTHREAD_SCOPE_PROCESS will return a nonzero value.

We demonstrated the usage of the two functions previously in the native method jni_thread_scope. We created two processes in the method. The child process runs five threads, and the parent process only runs a single thread. Because they are all system scope threads, the threads are scheduled to get roughly same amount of CPU time slices regardless of the process they belong to, and therefore they finish at roughly the same time as shown in the first logcat screenshot in step 6 of the How to do it... section of this recipe.

Note

We called fork to create a process in our code. This is for demonstration purpose. It is strongly discouraged to create a native process with fork on Android because the native process won't be managed by the Android framework and a misbehaving native process can consume lots of CPU cycles and cause security vulnerabilities.

Scheduling policy and thread priority

Each thread has an associated scheduling policy and priority. A thread with higher priority is more likely to be selected by the scheduler when a CPU is available. In case multiple threads have the same priority, the scheduling policy will determine how to schedule them. The policies defined in Android pthread.h include SCHED_OTHER, SCHED_FIFO, and SCHED_RR.

Note

The valid range of priority values is associated with the scheduling policy.

SCHED_FIFO : In the First In First Out (FIFO) policy, a thread gets the CPU until it exits or blocks. If blocked, it is placed at the end of the queue for its priority and the front thread in the queue will be given to the CPU. The priority range allowed under this policy is 1 to 99.

SCHED_RR: The Round Robin (RR) policy is similar to FIFO except that each thread is only allowed to run for a certain amount of time, known as quantum. When a thread finishes its quantum, it is interrupted and placed at the end of the queue for its priority. The priority range allowed under this policy is also 1 to 99.

SCHED_OTHER: This is the default scheduling policy. It also allows a thread to run only a limited amount of times, but the algorithm can be different and more complicated than SCHED_RR. All threads under this policy have a priority of 0.

People who are experienced with pthreads programming may be familiar with the pthreads policy and priority functions including:

  • pthread_attr_setschedpolicy
  • pthread_attr_getschedpolicy
  • pthread_attr_setschedparam
  • pthread_attr_getschedparam

These functions do not work on Android, as expected, although they are defined in the Android pthread.h header. Therefore, we will not discuss the details here.

In our sample project, we implemented a native method jni_thread_fifo, which attempts to set the scheduling policy as SCHED_FIFO and the thread priority. As shown in the second logcat screenshot, the threads are not affected by these settings.

In summary, all Android threads are system scope threads with 0 priority, and scheduling policy SCHED_OTHER.

Scheduling using nice value/level

Nice value/level is another factor that can affect the scheduler. It is also often referred to as priority, but here we will use nice value to differentiate it with the thread priority we discussed earlier.

We use the following two approaches to adjust the nice value:

  • Calling setpriority: This is demonstrated in SetPriority.cpp. We created five threads with different nice values, and the third logcat screenshot in step 8 of the How to do it section indicates the thread with lower nice values return first.
  • Calling android.os.Process.setThreadPriority: This is illustrated in JNIProcessSetThreadPriority.cpp. As shown in the fourth logcat screenshot at step 9 of the How to do it section, we got similar result as calling setpriority. In fact, setThreadPriority calls setpriority internally.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset