This recipe discusses how to schedule native threads at Android NDK.
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.
The following steps will help us create an Android project that demonstrates threads scheduling at Android NDK:
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.MainActivity.java
under package cookbook.chapter6.nativethreadsschedule
. This Java file simply loads the native library NativeThreadsSchedule
and calls the native methods.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: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); }
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); }
JNIProcessSetThreadPriority.cpp
file contains the source code to configure thread nice value through the android.os.P
rocess.setThreadPriority
Java methodThe 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(); }
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)
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:
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: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: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:We can schedule native threads by setting the scheduling contention scope, thread priority, and scheduling policy:
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.
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.
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.
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
.
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
.
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:
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.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.