Thread Scheduling in Android

Posted

This post will give an overview of how thread scheduling works in Android, and will briefly demonstrate how to explicitly set thread priorities yourself to ensure that your application remains responsive even as multiple threads run in the background.

For those who are unfamiliar with the term, a thread scheduler is the part of the operating system in charge of deciding which threads in the system should run, when, and for how long. Android’s thread scheduler uses two main factors to determine how threads are scheduled across the entire system: nice values and cgroups.

Nice values

Similar to how they are used in Linux’s completely fair scheduling policy, nice values in Android are used as a measure of a thread’s priority. Threads with a higher nice value (i.e., lower priority, as in they are being “nice” to other threads in the system) will run less often than those with lower nice values (i.e., higher priority). The two most important of these are the default and background thread priorities. Intuitively, thread priorities should be chosen inverse-proportionally to the amount of work the thread is expected to do: the more work the thread will do, the less favorable thread priority it should get so that it doesn’t starve the system. For this reason, user interface threads (such as the main thread of a foreground Activity) are typically given a default priority, whereas background threads (such as a thread executing an AsyncTask) are typically given a background priority.

Nice values are theoretically important because they help reduce background work that might otherwise interrupt the user interface. In practice, however, they alone are not sufficient. For example, consider twenty background threads and a single foreground thread driving the UI. Despite their low individual priorities, collectively the twenty background threads will still likely impact the performance of the single foreground thread, resulting in lag and hurting the user experience. Since at any given moment several applications could potentially have background threads waiting to run, the Android OS must somehow address these scenarios. Enter cgroups.

Cgroups

To address this problem, Android enforces an even stricter foreground vs. background scheduling policy using Linux cgroups (control groups). Threads with background priorities are implicitly moved into a background cgroup, where they are limited to only a small percentage1 of the available CPU if threads in other groups are busy. This separation allows background threads to make some forward progress, without having enough of an impact on the foreground threads to be visible to the user.

In addition to automatically assigning low-priority threads to the background cgroup, Android ensures that all threads belonging to applications not currently running in the foreground are implicitly moved to the background cgroup as well. This automatic assignment of application threads to cgroups ensures that the current foreground application thread will always be the priority, regardless of how many applications are running in the background.

Setting Priorities with Process#setThreadPriority(int)

For the most part, the Android APIs already assign worker threads a background priority for you (for example, see the source code for HandlerThread and AsyncTask). It is important to remember, however, that this will not always be the case. Threads and ExecutorServices that are instantiated on the main UI thread, for example, will inherit a default, foreground priority, making lag more likely and possibly hurting the application’s performance. In these cases, you should always remember to set the thread’s priority by calling Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND) before the Thread is run. Doing so is straightforward, as shown in the example below:

new Thread(new Runnable() {
  @Override
  public void run() {
    Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);

    // ...
  }
}).start();

As always, thanks for reading, and leave a comment if you have any questions. Don’t forget to +1 this blog post too!


1 This percentage was 5% at the time of this writing, though it is possible that this value could change in the future. As of Android 4.4.2, cgroup mount points are created and initialized at boot-up in /system/core/rootdir/init.rc (see lines 100-123).

+1 this blog!

Android Design Patterns is a website for developers who wish to better understand the Android application framework. The tutorials here emphasize proper code design and project maintainability.

Find a typo?

Submit a pull request! The code powering this site is open-source and available on GitHub. Corrections are appreciated and encouraged! Click here for instructions.

Apps by me

Shape Shifter simplifies the creation of AnimatedVectorDrawable path morphing animations. View on GitHub.
2048++ is hands down the cleanest, sleekest, most responsive 2048 app for Android!