13.3 At-Most-Once and At-Least-Once-Execution

Team-FLY

13.3 At-Most-Once and At-Least-Once-Execution

If a mutex isn't statically initialized , the program must call pthread_mutex_init before using any of the other mutex functions. For programs that have a well-defined initialization phase before they create additional threads, the main thread can perform this initialization. Not all problems fit this structure. Care must be taken to call pthread_mutex_init before any thread accesses a mutex, but having each thread initialize the mutex doesn't work either. The effect of calling pthread_mutex_init for a mutex that has already been initialized is not defined.

The notion of single initialization is so important that POSIX provides the pthread_once function to ensure these semantics. The once_control parameter must be statically initialized with PTHREAD_ONCE_INIT . The init_routine is called the first time pthread_once is called with a given once_control , and init_routine is not called on subsequent calls. When a thread returns from pthread_once without error, the init_routine has been completed by some thread.

  SYNOPSIS  #include <pthread.h>   int pthread_once(pthread_once_t *once_control,                    void (*init_routine)(void));   pthread_once_t once_control = PTHREAD_ONCE_INIT;  POSIX:THR  

If successful, pthread_once returns 0. If unsuccessful , pthread_once returns a nonzero error code. No mandatory errors are defined for pthread_once .

Program 13.10 uses pthread_once to implement an initialization function printinitmutex . Notice that var isn't protected by a mutex because it will be changed only once by printinitonce , and that modification occurs before any caller returns from printinitonce .

Program 13.10 printinitonce.c

A function that uses pthread_once to initialize a variable and print a statement at most once .

 #include <pthread.h> #include <stdio.h> static pthread_once_t initonce = PTHREAD_ONCE_INIT; int var; static void initialization(void) {    var = 1;    printf("The variable was initialized to %d\n", var); } int printinitonce(void) {        /* call initialization at most once */    return pthread_once(&initonce, initialization); } 

The initialization function of printinitonce has no parameters, making it hard to initialize var to something other than a fixed value. Program 13.11 shows an alternative implementation of at-most-once initialization that uses a statically initialized mutex. The printinitmutex function performs the initialization and printing at most once regardless of how many different variables or values are passed. If successful, printinitmutex returns 0. If unsuccessful, printinitmutex returns a nonzero error code. The mutex in printinitmutex is declared in the function so that it is accessible only inside the function. Giving the mutex static storage class guarantees that the same mutex is used every time the function is called.

Program 13.11 printinitmutex.c

A function that uses a statically initialized mutex to initialize a variable and print a statement at most once .

 #include <pthread.h> #include <stdio.h> int printinitmutex(int *var, int value) {    static int done = 0;    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;    int error;    if (error = pthread_mutex_lock(&lock))       return error;    if (!done) {       *var = value;       printf("The variable was initialized to %d\n", value);       done = 1;    }    return pthread_mutex_unlock(&lock); } 
Example 13.9

The following code segment initializes whichiteration to the index of the first loop iteration in which dostuff returns a nonzero value.

 int whichiteration = -1; void *thisthread(void *) {    int i;    for (i = 0; i < 100; i++)       if (dostuff())          printinitmutex(&whichiteration, i); } 

The whichiteration value is changed at most once, even if the program creates several threads running thisthread .

The testandsetonce function of Program 13.12 atomically sets an internal variable to 1 and returns the previous value of the internal variable in its ovalue parameter. The first call to testandsetonce initializes done to 0, sets *ovalue to 0 and sets done to 1. Subsequent calls set *ovalue to 1. The mutex ensures that no two threads have ovalue set to 0. If successful, testandsetonce returns 0. If unsuccessful, testandsetonce returns a nonzero error code.

Exercise 13.10

What happens if you remove the static qualifier from the done and lock variables of testandsetonce of Program 13.12?

Answer:

The static qualifier for variables inside a block ensures that they remain in existence for subsequent executions of the block. Without the static qualifier, done and lock become automatic variables. In this case, each call to testandsetonce allocates new variables and each return deallocates them. The function no longer works.

Program 13.12 testandsetonce.c

A function that uses a mutex to set a variable to 1 at most once .

 #include <pthread.h> int testandsetonce(int *ovalue) {    static int done = 0;    static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;    int error;    if (error = pthread_mutex_lock(&lock))       return error;    *ovalue = done;    done = 1;    return pthread_mutex_unlock(&lock); } 
Exercise 13.11

Does testandsetonce still work if you move the declarations of done and lock outside the testandsetonce function?

Answer:

Yes, testandsetonce still works. However, now done and lock are accessible to other functions defined in the same file. Keeping them inside the function is safer for enforcing at-most-once semantics.

Exercise 13.12

Does the following use of testandsetonce of Program 13.12 ensure that the initialization of var and the printing of the message occur at most once?

 int error; int oflag; int var; error = testandsetonce(&oflag); if (!error && !oflag) {    var = 1;    printf("The variable has been initialized to 1\n"); } var++; 

Answer:

No. Successive calls to testandsetonce of Program 13.12 can return before the variable has been initialized. Consider the following scenario in which var must be initialized before being incremented.

  1. Thread A calls testandsetonce .

  2. The testandsetonce returns in thread A.

  3. Thread A loses the CPU.

  4. Thread B calls testandsetonce .

  5. The executeonce returns to thread B without printing or initializing var .

  6. Thread B assumes that var has been initialized, and it increments the variable.

  7. Thread A gets the CPU again and initializes var to 1.

    In this case, var should have the value 2 since it was initialized to 1 and incremented once. Unfortunately, it has the value 1.

The strategies discussed in this section guarantee at-most-once execution. They do not guarantee that code has been executed at least once. At-least-once semantics are important for initialization. For example, suppose that you choose to use pthread_mutex_init rather than the static initializer to initialize a mutex. You need both at-most-once and at-least-once semantics. In other words, you need to perform an operation such as initialization exactly once . Sometimes the structure of the program ensures that this is the case ”a main thread performs all necessary initialization before creating any threads. In other situations, each thread must call initialization when it starts executing, or each function must call the initialization before accessing the mutex. In these cases, you will need to use at-most-once strategies in conjunction with the calls.

Team-FLY


Unix Systems Programming
UNIX Systems Programming: Communication, Concurrency and Threads
ISBN: 0130424110
EAN: 2147483647
Year: 2003
Pages: 274

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net