This recipe discusses how to use pthread mutex at Android NDK.
The following steps help to create an Android project that demonstrates the usage of pthread mutex:
NativeThreadsMutex
. Set the package name as cookbook.chapter6.nativethreadsmutex
. Refer to the Loading native libraries and registering native methods recipe in Chapter 2, Java Native Interface for more detailed instructions.MainActivity.java
under the cookbook.chapter6.nativethreadsmutex
package. This Java file simply loads the native NativeThreadsMutex
library and calls the native jni_start_threads
method.mylog.h
and NativeThreadsMutex.cpp
in the jni
folder. NativeThreadsMutex.cpp
contains the code to start two threads. The two threads will update a shared counter. A part of the code is shown as follows:The run_by_thread1
function is executed by the first native thread:
int cnt = 0; int THR = 10; void *run_by_thread1(void *arg) { int* threadNum = (int*)arg; while (cnt < THR) { pthread_mutex_lock(&mux1); while ( pthread_mutex_trylock(&mux2) ) { pthread_mutex_unlock(&mux1); //avoid deadlock usleep(50000); //if failed to get mux2, release mux1 first pthread_mutex_lock(&mux1); } ++cnt; LOGI(1, "thread %d: cnt = %d", *threadNum, cnt); pthread_mutex_unlock(&mux1); pthread_mutex_unlock(&mux2); sleep(1); } }
The run_by_thread2
function is executed by the second native thread:
void *run_by_thread2(void *arg) { int* threadNum = (int*)arg; while (cnt < THR) { pthread_mutex_lock(&mux2); while ( pthread_mutex_trylock(&mux1) ) { pthread_mutex_unlock(&mux2); //avoid deadlock usleep(50000); //if failed to get mux2, release mux1 first pthread_mutex_lock(&mux2); } ++cnt; LOGI(1, "thread %d: cnt = %d", *threadNum, cnt); pthread_mutex_unlock(&mux2); pthread_mutex_unlock(&mux1); sleep(1); } }
Android.mk
file in the jni
folder with the following content:LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := NativeThreadsMutex LOCAL_SRC_FILES := NativeThreadsMutex.cpp LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY)
logcat
output.$ adb logcat -v time NativeThreadsMutex:I *:S
The logcat
output is shown as follows:
jni_start_threads_dead
in NativeThreadsMutex.cpp
, which can probably cause a deadlock (we may need to run the code a few times to produce the deadlock situation). If we call jni_start_threads_dead
in MainActivity.java
, the two threads will start and then block as shown in the following logcat
output:As indicated in this screenshot, the two threads cannot proceed after started.
The sample project demonstrates how to use mutex to synchronize native threads. We describe the details as follows:
A mutex can be initialized with the pthread_mutex_init
function, which has the following prototype:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
The input argument mutex is a pointer to the mutex to be initialized and attr
indicates the attributes of mutex. If attr
is set to NULL
, the default attributes will be used. The function will return a zero if the mutex is initialized successfully and a non-zero value otherwise.
When we are done with the mutex, we can destroy it with the pthread_mutex_destroy
function, which has the following prototype:
int pthread_mutex_destroy(pthread_mutex_t *mutex);
The input argument is a pointer pointing to the mutex to be destroyed.
In our sample project, we created two mutexes mux1
and mux2
to synchronize the access of a shared counter cnt
by the two threads. After the two threads exit, we destroyed the mutexes.
The following four functions are available to lock and unlock a mutex:
int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_lock_timeout_np(pthread_mutex_t *mutex, unsigned msecs);
In all the four functions, the input argument refers to the mutex
object in use. A zero return value indicates the mutex is locked or unlocked successfully. The last function allows us to specify a wait timeout in milliseconds. If it cannot acquire the mutex after the timeout, it will return EBUSY
to indicate failure.
We demonstrated the usage of the functions previously in our example. In function run_by_thread1
, we first lock mux1
by pthread_mutex_lock
, and then mux2
by pthread_mutex_trylock
. If mux2
cannot be locked, we unlock mux1
, sleep for 50 milliseconds, and try again. If mux2
can be locked, we update the shared counter cnt
, log its current value, and then release the two mutexes. Another function run_by_thread2
is similar to run_by_thread1
, except that it locks mux2
first, and then mux1
. The two functions are executed by two threads. This can be illustrated by the following diagram:
As shown in the preceding diagram, thread 1 needs to obtain mux1
, then mux2
in order to update cnt
, while thread 2 needs to acquire mux2
, then mux1
to update cnt
. In case thread 1 locked mux1
and thread 2 locked mux2
, neither threads can proceed. This corresponds to the situation where pthread_mutex_trylock
returns a nonzero value. If this happens, one thread will give up its mutex so the other thread can proceed to update the shared counter cnt
and release the two mutexes. Note that we can replace the pthread_mutex_trylock
with pthread_mutex_lock_timeout_np
in our code. Readers are encouraged to try it out themselves.
We also implemented a native method jni_start_threads_dead
which will probably cause a deadlock. The thread setup is similar to the previous case, but we use pthread_mutex_lock
instead of pthread_mutex_trylock
, and the threads do not give up the mutexes they have already locked. This can be illustrated as shown in the following diagram:
Thread 1 tries to lock mux1
and then mux2
, while thread 2 tries to lock mux2
and then mux1
. In case where thread 1 has locked mux1
and thread 2 has locked mux2
, none of the threads can proceed. Because they won't give up the mutexes they've obtained, the two threads will be blocked forever. This is referred to as a deadlock.
Recall that second input argument for function
pthread_mutex_init
is a pointer to pthread_mutexattr_t
. A few functions are defined in pthread.h
to initialize, manipulate, and destroy mutex attributes, including:
pthread_mutexattr_init
pthread_mutexattr_destroy
pthread_mutexattr_gettype
pthread_mutexattr_settype
pthread_mutexattr_setpshared
pthread_mutexattr_getpshared
Interested readers can look into the pthread.h
header file for more information.