13.3 At-Most-Once and At-Least-Once-ExecutionIf 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.
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.cA function that uses pthread_once to initialize a variable and print a statement at most once.
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.cA function that uses a statically initialized mutex to initialize a variable and print a statement at most once.
Example 13.9The following code segment initializes whichiteration to the index of the first loop iteration in which dostuff returns a nonzero value.
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.10What 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.cA function that uses a mutex to set a variable to 1 at most once.
Exercise 13.11Does 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.12Does 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?
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.
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 casea 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. |