Synchronizing native threads with semaphore at Android NDK

We have covered mutex, conditional variables, and reader/writer lock in the previous three recipes. This is the last recipe on threads synchronization at Android NDK, and we will discuss semaphores.

Getting ready...

Readers are expected to read through the previous three recipes, Synchronizing native threads with mutex at Android NDK, Synchronizing native threads with conditional variables at Android NDK, and Synchronizing native threads with reader/writer locks at Android NDK, before this one.

How to do it...

The following steps will help you create an Android project that demonstrates the usage of pthread reader/writer lock:

  1. Create an Android application named NativeThreadsSemaphore. Set the package name as cookbook.chapter6.nativethreadssemaphore. Refer to the Loading native libraries and registering native methods recipe in Chapter 2, Java Native Interface for more detailed instructions.
  2. Right-click on the project NativeThreadsSemaphore, select Android Tools | Add Native Support.
  3. Add a Java file named MainActivity.java under package cookbook.chapter6.nativethreadssemaphore. This Java file simply loads the native library NativeThreadsSemaphore and calls the native jni_start_threads method.
  4. Add two files named mylog.h and NativeThreadsSemaphore.cpp under the jni folder. A part of the code in NativeThreadsSemaphore.cpp is shown as follows:

    jni_start_threads creates pNumOfConsumer number of consumer threads, pNumOfProducer number of producer threads, and numOfSlots number of slots:

    void jni_start_threads(JNIEnv *pEnv, jobject pObj, int pNumOfConsumer, int pNumOfProducer, int numOfSlots) {
      pthread_t *ths;
      int i, ret;
      int *thNum;
      pthread_mutex_init(&mux, NULL);
      sem_init(&emptySem, 0, numOfSlots);
      sem_init(&fullSem, 0, 0);
      ths = (pthread_t*)malloc(sizeof(pthread_t)*(pNumOfConsumer+pNumOfProducer));
      thNum = (int*)malloc(sizeof(int)*(pNumOfConsumer+pNumOfProducer));
      for (i = 0; i < pNumOfConsumer + pNumOfProducer; ++i) {
        thNum[i] = i;
        if (i < pNumOfConsumer) {
          ret = pthread_create(&ths[i], NULL, 
    un_by_consumer_thread, (void*)&(thNum[i]));
        } else {
          ret = pthread_create(&ths[i], NULL, run_by_producer_thread, (void*)&(thNum[i]));
        }
      }
      for (i = 0; i < pNumOfConsumer+pNumOfProducer; ++i) {
        ret = pthread_join(ths[i], NULL);
      }
      sem_destroy(&emptySem);
      sem_destroy(&fullSem);
      pthread_mutex_destroy(&mux);
      free(thNum);
      free(ths);
    }

    run_by_consumer_thread is the function executed by the consumer thread:

    void *run_by_consumer_thread(void *arg) {
      int* threadNum = (int*)arg;
      int i;
      for (i = 0; i < 4; ++i) {
        sem_wait(&fullSem);
        pthread_mutex_lock(&mux);
        --numOfItems;
        pthread_mutex_unlock(&mux);
        sem_post(&emptySem);
      }
      return NULL;
    }

    run_by_producer_thread is the function executed by producer thread:

    void *run_by_producer_thread(void *arg) {
      int* threadNum = (int*)arg;
      int i;
      for (i = 0; i < 4; ++i) {
        sem_wait(&emptySem);
        pthread_mutex_lock(&mux);
        ++numOfItems;
        pthread_mutex_unlock(&mux);
        sem_post(&fullSem);
      }
      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    := NativeThreadsSemaphore
    LOCAL_SRC_FILES := NativeThreadsSemaphore.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 NativeThreadsSemaphore:I *:S

    The logcat output is shown in the following screenshot:

    How to do it...

How it works...

Semaphores are essentially integer counters. Two primary operations are supported by a semaphore:

  • Wait: It attempts to decrement the semaphore value. If wait is called on a semaphore of value zero, the calling thread is blocked until post is called somewhere else to increment semaphore value.
  • Post: It attempts to increment the semaphore value.

The semaphore related functions are defined in semaphore.h rather than pthread.h. Next, we describe a few key functions.

Note

Interprocess mutex, conditional variable, and semaphore are not supported on Android. Android uses Intent, Binder, and so on for interprocess communication and synchronization.

Initialize and destroy a semaphore

The following three functions are defined to initialize or destroy a semaphore:

extern int sem_init(sem_t *sem, int pshared, unsigned int value);
extern int    sem_init(sem_t *, int, unsigned int value);
extern int    sem_destroy(sem_t *);

The first two functions are used to initialize a semaphore. They both initialize the semaphore pointed by the input argument sem with the value indicated by the argument value. The first function also accepts an argument pshared, which should be set to zero for thread synchronization. If it is set to nonzero, the semaphore can be shared between processes, which is not supported on Android and therefore not discussed.

Using a semaphore

The following functions are defined to use a semaphore.

extern int    sem_trywait(sem_t *);
extern int    sem_wait(sem_t *);
extern int    sem_post(sem_t *);
extern int    sem_getvalue(sem_t *, int *);

The first two functions are used to wait on a semaphore. If the semaphore value is not zero, then the value is decreased by one. If the value is zero, the first function will return a nonzero value to indicate failure, while the second function will block the calling thread. The third function is used to increase the semaphore value by one, and the last function is used to query the value of the semaphore. Note that the value is returned through the second input argument rather than the return value.

Note

Android semaphore.h also defines a function named sem_timedwait to allow us to specify a timeout value while waiting on a semaphore.

In our sample project, we used two semaphores emptySem and fullSem, and a mutex mux. The app will create a few producer threads and consumer threads. The emptySem semaphore is used to indicate the number of slots available to store the items produced by the producer thread, while fullSem refers to the number of items for the consumer thread to consume. The mutex mux is used to ensure no two threads can access the shared counter numOfItems at one time.

The producer thread will need to wait on the emptySem semaphore. When it is unblocked, the producer has obtained an empty slot. It will lock mux and then update the shared count numOfItems, which means a new item has been produced. Therefore, it will call the post function on fullSem to increment its value.

On the other hand, the consumer thread will wait on fullSem. When it is unblocked, the consumer has consumed an item. It will lock mux and then update the shared count numOfItems. A new empty slot is available because of the item consumed, so the consumer thread will call post on emptySem to increment its value.

Note

Mutex mux can also be replaced by a binary semaphore. The possible values of a binary semaphore are restricted to zero and one.

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

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