There are several options when we want to preserve thread-wide data across functions, including global variables, argument passing, and thread-specific data key. This recipe discusses all the three options with a focus on thread-specific data key.
Readers are recommended to read the Creating and terminating native threads at Android NDK recipe and the Synchronizing native threads with mutex at Android NDK recipe in this chapter before this one.
The following steps will help us create an Android project that demonstrates data management at Android NDK:
NativeThreadsData
. Set the package name as cookbook.chapter6.nativethreadsdata
. Please refer to the Loading native libraries and registering native methods recipe in Chapter 2, Java Native Interface if you want more detailed instructions.MainActivity.java
under package cookbook.chapter6.nativethreadsdata
. This Java file simply loads the native library NativeThreadsData
and calls the native methods.mylog.h
and NativeThreadsData.cpp
files under the jni
folder. The mylog.h
contains the Android native logcat
utility functions, while the NativeThreadsData.cpp
file contains the native code to start multiple threads. A part of the code is shown as follows:jni_start_threads
starts n number of threads, where n is specified by the variable pNumOfThreads
:
void jni_start_threads(JNIEnv *pEnv, jobject pObj, int pNumOfThreads) { pthread_t *ths; int i, ret; int *thNum; ths = (pthread_t*)malloc(sizeof(pthread_t)*pNumOfThreads); thNum = (int*)malloc(sizeof(int)*pNumOfThreads); pthread_mutex_init(&mux, NULL); pthread_key_create(&muxCntKey, free_muxCnt); for (i = 0; i < pNumOfThreads; ++i) { thNum[i] = i; ret = pthread_create(&ths[i], NULL, run_by_thread, (void*)&(thNum[i])); } for (i = 0; i < pNumOfThreads; ++i) { ret = pthread_join(ths[i], NULL); } pthread_key_delete(muxCntKey); pthread_mutex_destroy(&mux); free(thNum); free(ths); }
The thread_step_1
function is executed by threads. It gets the data associated with the thread-
specific key and uses it to count the number of times the mutex is locked:
void thread_step_1() { struct timeval st, cu; long stt, cut; int *muxCntData = (int*)pthread_getspecific(muxCntKey); gettimeofday(&st, NULL); stt = st.tv_sec*1000 + st.tv_usec/1000; do { pthread_mutex_lock(&mux); (*muxCntData)++; pthread_mutex_unlock(&mux); gettimeofday(&st, NULL); cut = st.tv_sec*1000 + st.tv_usec/1000; } while (cut - stt < 10000); }
The thread_step_2
function is executed by threads. It gets the data associated with the thread-specific key and prints it out:
void thread_step_2(int thNum) { int *muxCntData = (int*)pthread_getspecific(muxCntKey); LOGI(1, "thread %d: mux usage count: %d ", thNum, *muxCntData); }
The run_by_thread
function
is executed by threads:
void *run_by_thread(void *arg) { int* threadNum = (int*)arg; int *muxCntData = (int*)malloc(sizeof(int)); *muxCntData = 0; pthread_setspecific(muxCntKey, (void*)muxCntData); thread_step_1(); thread_step_2(*threadNum); return NULL; }
Android.mk
file under the jni
folder with the following content:LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := NativeThreadsData LOCAL_SRC_FILES := NativeThreadsData.cpp LOCAL_LDLIBS := -llog include $(BUILD_SHARED_LIBRARY)
logcat
output:$ adb logcat -v time NativeThreadsData:I *:S
The logcat
output is shown in the following screenshot:
In our sample project, we demonstrated passing data using global variables, argument, and thread-specific data key:
mux
is declared as a global variable, and each thread can access it.run_by_thread
, each thread passes the accepted thread number to another function thread_step_2
.muxCntKey
. Each thread can associate its own value with the key. In our code, we used this key to store the number of times a thread locks the mutex mux
.Next we'll discuss the thread-specific data key in detail.
The following two functions are defined in
pthread.h
to create and delete a thread-specific data key respectively:
int pthread_key_create(pthread_key_t *key, void (*destructor_function)(void *)); int pthread_key_delete (pthread_key_t key);
pthread_key_create
accepts a pointer to the pthread_key_t
structure and a function pointer to a destruction function to be associated with each key value. The destruction function is optional and can be set to NULL
. In our example, we called pthread_key_create
to create the key named muxCntKey
.
The pthread_key_create
function returns zero to indicate success and some other values for failure. If successful, the first input argument key
will be pointing to the newly created key, and the value NULL
is associated with the new key in all active threads. If a new thread is created after the key creation, the value NULL
is also associated with the key for the new thread.
When a thread exits, the associated value of the key is set to NULL
, and then the destruction function associated with the key is called with the key's previously associated value as the sole input argument. In our sample code, we defined a destruction function free_muxCnt
to free the memory of data associated with the key muxCntKey
.
pthread_key_delete
is relatively simple. It accepts a key created by pthread_key_create
and deletes it. It returns zero for success and a nonzero value for failure.
Android pthread.h
defines the following two functions for thread-specific data management:
int pthread_setspecific(pthread_key_t key, const void *value); void *pthread_getspecific(pthread_key_t key);
The
pthread_setspecific
function accepts a previously created data key and a pointer to data to be associated with the key. It returns a zero to indicate success and nonzero otherwise. Different threads can call this function to bind different values to the same key.
pthread_getspecific
accepts a previously created data and key and returns a pointer to the data associated with the key in the calling thread.
In the run_by_thread
function of our sample code, we associate an integer variable initialized to zero to the muxCntKey
key. In function thread_step_1
, we get the integer variable associated with the key and use it to count the number of times mux
is locked. In function thread_step_2
, we again obtain the integer variable associated with muxCntKey
and print its value.