Managing data for native threads at Android NDK

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.

Getting ready...

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.

How to do it...

The following steps will help us create an Android project that demonstrates data management at Android NDK:

  1. Create an Android application named 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.
  2. Right-click on the project NativeThreadsData, select Android Tools | Add Native Support.
  3. Add a Java file named MainActivity.java under package cookbook.chapter6.nativethreadsdata. This Java file simply loads the native library NativeThreadsData and calls the native methods.
  4. Add 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;
    }
  5. Add an 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)
  6. Build and run the Android project, and use the following command to monitor the logcat output:
    $ adb logcat -v time NativeThreadsData:I *:S

    The logcat output is shown in the following screenshot:

    How to do it...

How it works...

In our sample project, we demonstrated passing data using global variables, argument, and thread-specific data key:

  • The mutex mux is declared as a global variable, and each thread can access it.
  • Each thread is assigned a thread number as input argument. In the function run_by_thread, each thread passes the accepted thread number to another function thread_step_2.
  • We defined a thread-specific key 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.

Creation and deletion of thread-specific data key

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.

Set and get thread-specific data

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.

..................Content has been hidden....................

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