NAME | SYNOPSIS | DESCRIPTION | RETURN VALUES | ERRORS | EXAMPLES | ATTRIBUTES | SEE ALSO | NOTES
cc -mt [ flag... ] file...[ library... ] #include <thread.h> #include <synch.h>int mutex_init(mutex_t *mp, int type, void * arg);
Mutual exclusion locks (mutexes) prevent multiple threads from simultaneously executing critical sections of code which access shared data (that is, mutexes are used to serialize the execution of threads). All mutexes must be global. A successful call for a mutex lock by way of mutex_lock() will cause another thread that is also trying to lock the same mutex to block until the owner thread unlocks it by way of mutex_unlock(). Threads within the same process or within other processes can share mutexes.
Mutexes can synchronize threads within the same process or in other processes. Mutexes can be used to synchronize threads between processes if the mutexes are allocated in writable memory and shared among the cooperating processes (see mmap(2)), and have been initialized for this task.
Mutexes are either intra-process or inter-process, depending upon the argument passed implicitly or explicitly to the initialization of that mutex. A statically allocated mutex does not need to be explicitly initialized; by default, a statically allocated mutex is initialized with all zeros and its scope is set to be within the calling process.
For inter-process synchronization, a mutex needs to be allocated in memory shared between these processes. Since the memory for such a mutex must be allocated dynamically, the mutex needs to be explicitly initialized using mutex_init().
The mutex_init() function initializes the mutex referenced by mp with the type specified by type. Upon successful initialization the state of the mutex becomes initialized and unlocked. No current type uses arg although a future type may specify additional behavior parameters by way of arg. type may be one of the following:
The mutex can synchronize threads only in this process. arg is ignored.
The mutex can synchronize threads in this process and other processes. arg is ignored. The object initialized with this attribute must be allocated in memory shared between processes, either in System V shared memory (see shmop(2)) or in memory mapped to a file (see mmap(2)). If the object is not allocated in such shared memory, it will not be shared between processes.
The mutex can synchronize threads in this process and other processes robustly. At the time of process death, if the lock is held by the process, it is unlocked. The next owner of this mutex will acquire it with an error return of EOWNERDEAD. Note that the application must always check the return code from mutex_lock() for a mutex of this type. The new owner of this mutex should then attempt to make the state protected by the mutex consistent, since this state could have been left inconsistent when the last owner died. If the new owner is able to make the state consistent, it should re-initialize the mutex and then unlock the mutex. If the new owner is not able to make the state consistent, for whatever reason, it should not re-initialize the mutex, but should just unlock the mutex. If the latter event occurs, all waiters will be woken up and all subsequent calls to mutex_lock() will fail in acquiring the mutex with an error code of ENOTRECOVERABLE. mutex can be made consistent by un-initializing the mutex (mutex_destroy()) and re-initializing it (mutex_init()). If the process which got the lock with EOWNERDEAD died, the next owner will get the lock with an error return of EOWNERDEAD. arg is ignored. The object initialized with this attribute must be allocated in memory shared between processes, either in System V shared memory (see shmop(2)) or in memory mapped to a file (see mmap(2)) and memory must be zeroed before initialization. All the processes interested in the robust lock must call mutex_init() at least once to register robust mutex with the system and potentially initialize it. If the object is not allocated in such shared memory, it will not be shared between processes. If mutex_init() is called on a previously initialized mutex mutex_init() will not re-initialize the mutex.
Initializing mutexes can also be accomplished by allocating in zeroed memory (default), in which case, a type of USYNC_THREAD is assumed. The same mutex must not be simultaneously initialized by multiple threads. A mutex lock must not be re-initialized while in use by other threads. If default mutex attributes are used, the macro DEFAULTMUTEX can be used to initialize mutexes that are statically allocated.
mutex_t mp; mutex_init(&mp, NULL, NULL); OR mutex_init(&mp, USYNC_THREAD, NULL); OR mutex_t mp = DEFAULTMUTEX; OR mutex_t mp; mp = calloc(1, sizeof (mutex_t)); OR mutex_t mp; mp = malloc(sizeof (mutex_t)); memset(mp, 0, sizeof (mutex_t));
Customized mutex initialization (inter-process):
mutex_init(&mp, USYNC_PROCESS, NULL);
Customized mutex initialization (inter-process):
mutex_init(&mp, USYNC_PROCESS_ROBUST, NULL);
A critical section of code is enclosed by a the call to lock the mutex and the call to unlock the mutex to protect it from simultaneous access by multiple threads. Only one thread at a time may possess mutually exclusive access to the critical section of code that is enclosed by the mutex-locking call and the mutex-unlocking call, whether the mutex's scope is intra-process or inter-process. A thread calling to lock the mutex either gets exclusive access to the code starting from the successful locking until its call to unlock the mutex, or it waits until the mutex is unlocked by the thread that locked it.
Mutexes have ownership, unlike semaphores. Although any thread, within the scope of a mutex, can get an unlocked mutex and lock access to the same critical section of code, only the thread that locked a mutex should unlock it.
If a thread waiting for a mutex receives a signal, upon return from the signal handler, the thread resumes waiting for the mutex as if there was no interrupt. A mutex protects code, not data; therefore, strongly bind a mutex with the data by putting both within the same structure, or at least within the same procedure.
A call to mutex_lock() locks the mutex object referenced by mp. If the mutex is already locked, the calling thread blocks until the mutex is freed; this will return with the mutex object referenced by mp in the locked state with the calling thread as its owner. If the current owner of a mutex tries to relock the mutex, it will result in deadlock.
mutex_trylock() is the same as mutex_lock(), respectively, except that if the mutex object referenced by mp is locked (by any thread, including the current thread), the call returns immediately with an error.
mutex_unlock() are called by the owner of the mutex object referenced by mp to release it. The mutex must be locked and the calling thread must be the one that last locked the mutex (the owner). If there are threads blocked on the mutex object referenced by mp when mutex_unlock() is called, the mp is freed, and the scheduling policy will determine which thread gets the mutex. If the calling thread is not the owner of the lock, no error status is returned, and the behavior of the program is undefined.
mutex_destroy() destroys the mutex object referenced by mp; the mutex object becomes uninitialized. The space used by the destroyed mutex variable is not freed. It needs to be explicitly reclaimed.
If successful, these functions return 0. Otherwise, an error number is returned.
These functions may fail if:
mp points to an illegal address.
The mutex_init() function will fail if:
The value specified by type is invalid.
The mutex_init() function will fail for USYNC_PROCESS_ROBUST type mutex if:
The mutex pointed to by mp was already initialized. An attempt to re-initialize a mutex previously initialized, but not yet destroyed.
The mutex_trylock() function will fail if:
The mutex pointed to by mp was already locked.
The mutex_lock() or mutex_trylock() functions will fail for USYNC_PROCESS_ROBUST type mutex if:
The last owner of this mutex died while holding the mutex. This mutex is now owned by the caller. The caller must now attempt to make the state protected by the mutex consistent. If it is able to cleanup the state, then it should re-initialize the mutex (see mutex_init()) and unlock the mutex. Subsequent calls to mutex_lock() will behave normally, as before. If the caller is not able to cleanup the state, the mutex should not be re-initialized, it should be unlocked. Subsequent calls to mutex_lock() will fail to acquire the mutex, with the error code, ENOTRECOVERABLE. If the owner who got the lock with EOWNERDEAD died, the next owner will get the lock with EOWNERDEAD.
The last owner of this mutex unmaped the mutex while holding the mutex. This mutex is now owned by the caller. The caller must now attempt to make the state protected by the mutex consistent. If it is able to cleanup the state, then it should re-initialize the mutex unlock the mutex. See mutex_init(3THR). Subsequent calls to mutex_lock() will behave normally, as before. If the caller is not able to cleanup the state, the mutex should not be re-initialized. Subsequent calls to mutex_lock() will fail to acquire the mutex with the error code, ENOTRECOVERABLE.
The mutex trying to be acquired is protecting state which has been left irrecoverable by the mutex's last owner, which died while holding the lock. The mutex has not been acquired. This condition can occur when the lock was previously acquired with EOWNERDEAD or ELOCKUNMAPPED and the owner was not able to cleanup the state and unlocked the mutex with out making the mutex consistent.
/* cc thisfile.c -lthread */ #define _REENTRANT #include <stdio.h> #include <thread.h> #define NUM_THREADS 12 void *change_global_data(void *); /* for thr_create() */ main(int argc,char * argv[]) { int i=0; for (i=0; i< NUM_THREADS; i++) { thr_create(NULL, 0, change_global_data, NULL, 0, NULL); } while ((thr_join(NULL, NULL, NULL) == 0)); } void * change_global_data(void *null) { static mutex_t Global_mutex; static int Global_data = 0; mutex_lock(&Global_mutex); Global_data++; sleep(1); printf("%d is global data\n",Global_data); mutex_unlock(&Global_mutex); return NULL; }
The previous example, the mutex, the code it owns, and the data it protects was enclosed in one function. The next example uses C++ features to accommodate many functions that use just one mutex to protect one data:
/* CC thisfile.c -lthread use C++ to compile*/ #define _REENTRANT #include <stdlib.h> #include <stdio.h> #include <thread.h> #include <errno.h> #include <iostream.h> #define NUM_THREADS 16 void *change_global_data(void *); /* for thr_create() */ class Mutected { private: static mutex_t Global_mutex; static int Global_data; public: static int add_to_global_data(void); static int subtract_from_global_data(void); }; int Mutected::Global_data = 0; mutex_t Mutected::Global_mutex; int Mutected::add_to_global_data() { mutex_lock(&Global_mutex); Global_data++; mutex_unlock(&Global_mutex); return Global_data; } int Mutected::subtract_from_global_data() { mutex_lock(&Global_mutex); Global_data--; mutex_unlock(&Global_mutex); return Global_data; } void main(int argc,char * argv[]) { int i=0; for (i=0;i< NUM_THREADS;i++) { thr_create(NULL,0,change_global_data,NULL,0,NULL); } while ((thr_join(NULL,NULL,NULL) == 0)); } void * change_global_data(void *) { static int switcher = 0; if ((switcher++ % 3) == 0) /* one-in-three threads subtracts */ cout << Mutected::subtract_from_global_data() << endl; else cout << Mutected::add_to_global_data() << endl; return NULL; }
A mutex can protect data that is shared among processes. The mutex would need to be initialized as USYNC_PROCESS. One process initializes the process-shared mutex and writes it to a file to be mapped into memory by all cooperating processes (see mmap(2)). Afterwards, other independent processes can run the same program (whether concurrently or not) and share mutex-protected data.
/* cc thisfile.c -lthread */ /* To execute, run the command line "a.out 0 & a.out 1" */ #define _REENTRANT #include <sys/types.h> #include <sys/mman.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <thread.h> #define INTERPROCESS_FILE "ipc-sharedfile" #define NUM_ADDTHREADS 12 #define NUM_SUBTRACTTHREADS 10 #define INCREMENT '0' #define DECREMENT '1' typedef struct { mutex_t Interprocess_mutex; int Interprocess_data; } buffer_t; buffer_t *buffer; void *add_interprocess_data(), *subtract_interprocess_data(); void create_shared_memory(), test_argv(); int zeroed[sizeof(buffer_t)]; int ipc_fd, i=0; void main(int argc,char * argv[]){ test_argv(argv[1]); switch (*argv[1]) { case INCREMENT: create_shared_memory(); ipc_fd = open(INTERPROCESS_FILE, O_RDWR); buffer = (buffer_t *)mmap(NULL, sizeof(buffer_t), PROT_READ|PROT_WRITE, MAP_SHARED, ipc_fd, 0); buffer->Interprocess_data = 0; mutex_init(&buffer->Interprocess_mutex, USYNC_PROCESS,0); for (i=0; i< NUM_ADDTHREADS; i++) thr_create(NULL, 0, add_interprocess_data, argv[1], 0, NULL); break; case DECREMENT: while((ipc_fd = open(INTERPROCESS_FILE, O_RDWR)) == -1) sleep(1); buffer = (buffer_t *)mmap(NULL, sizeof(buffer_t), PROT_READ|PROT_WRITE, MAP_SHARED, ipc_fd, 0); for (i=0; i< NUM_SUBTRACTTHREADS; i++) thr_create(NULL, 0, subtract_interprocess_data, argv[1], 0, NULL); break; } /* end switch */ while ((thr_join(NULL,NULL,NULL) == 0)); } /* end main */ void *add_interprocess_data(char argv_1[]){ mutex_lock(&buffer->Interprocess_mutex); buffer->Interprocess_data++; sleep(2); printf("%d is add-interprocess data, and %c is argv1\n", buffer->Interprocess_data, argv_1[0]); mutex_unlock(&buffer->Interprocess_mutex); return NULL; } void *subtract_interprocess_data(char argv_1[]) { mutex_lock(&buffer->Interprocess_mutex); buffer->Interprocess_data--; sleep(2); printf("%d is subtract-interprocess data, and %c is argv1\n", buffer->Interprocess_data, argv_1[0]); mutex_unlock(&buffer->Interprocess_mutex); return NULL; } void create_shared_memory(){ int i; ipc_fd = creat(INTERPROCESS_FILE, O_CREAT|O_RDWR ); for (i=0; i<sizeof(buffer_t); i++){ zeroed[i] = 0; write(ipc_fd, &zeroed[i],2); } close(ipc_fd); chmod(INTERPROCESS_FILE, S_IRWXU|S_IRWXG|S_IRWXO); } void test_argv(char argv1[]) { if (argv1 == NULL) { printf("use 0 as arg1 for initial process\n \ or use 1 as arg1 for the second process\n"); exit(NULL); } }
In this example, run the command line
a.out 0 & a.out 1
A mutex can protect data that is shared among processes robustly. The mutex would need to be initialized as USYNC_PROCESS_ROBUST. One process initializes the robust process-shared mutex and writes it to a file to be mapped into memory by all cooperating processes (see mmap(2)). Afterwards, other independent processes can run the same program (whether concurrently or not) and share mutex-protected data.
The following example shows how to use a USYNC_PROCESS_ROBUST type mutex.
/* cc thisfile.c -lthread */ /* To execute, run the command line "a.out & a.out 1" */ #include <sys/types.h> #include <sys/mman.h> #include <fcntl.h> #include <stdio.h> #include <thread.h> #define INTERPROCESS_FILE "ipc-sharedfile" typedef struct { mutex_t Interprocess_mutex; int Interprocess_data; } buffer_t; buffer_t *buffer; int make_date_consistent(); void create_shared_memory(); int zeroed[sizeof(buffer_t)]; int ipc_fd, i=0; main(int argc,char * argv[]) { int rc; if (argc > 1) { while((ipc_fd = open(INTERPROCESS_FILE, O_RDWR)) == -1) sleep(1); buffer = (buffer_t *)mmap(NULL, sizeof(buffer_t), PROT_READ|PROT_WRITE, MAP_SHARED, ipc_fd, 0); mutex_init(&buffer->Interprocess_mutex, USYNC_PROCESS_ROBUST,0); } else { create_shared_memory(); ipc_fd = open(INTERPROCESS_FILE, O_RDWR); buffer = (buffer_t *)mmap(NULL, sizeof(buffer_t), PROT_READ|PROT_WRITE, MAP_SHARED, ipc_fd, 0); buffer->Interprocess_data = 0; mutex_init(&buffer->Interprocess_mutex, USYNC_PROCESS_ROBUST,0); } for(;;) { rc = mutex_lock(&buffer->Interprocess_mutex); switch (rc) { case EOWNERDEAD: /* lock acquired. * last owner died holding the lock, try to make * the state associated with the mutex consistent. * If so, make the robust lock consistent by * re-initializing it. */ if (make_data_consistent()) mutex_init(&buffer->Interprocess_mutex, USYNC_PROCESS_ROBUST,0); mutex_unlock(&buffer->Interprocess_mutex); case ENOTRECOVERABLE: /* lock not acquired. * last owner got the mutex with EOWNERDEAD * mutex is not consistent (and data?), * so return from here */ exit(1); break; case 0: /* no error - data is consistent */ /* do something with data */ mutex_unlock(&buffer->Interprocess_mutex); break; } } } /* end main */ void create_shared_memory() { int i; ipc_fd = creat(INTERPROCESS_FILE, O_CREAT|O_RDWR ); for (i=0; i<sizeof(buffer_t); i++) { zeroed[i] = 0; write(ipc_fd, &zeroed[i],2); } close(ipc_fd); chmod(INTERPROCESS_FILE, S_IRWXU|S_IRWXG|S_IRWXO); } /* return 1 if able to make data consistent, otherwise 0. */ int make_data_consistent () { buffer->Interprocess_data = 0; return (1); }
struct record { int field1; int field2; mutex_t m; } *r; r = malloc(sizeof(struct record)); mutex_init(&r->m, USYNC_THREAD, NULL); /* * The fields in this record are accessed concurrently * by acquiring the embedded lock. */
The thread execution in this example is as follows:
Thread 1 executes: Thread 2 executes: ... ... mutex_lock(&r->m); mutex_lock(&r->m); r->field1++; localvar = r->field1; mutex_unlock(&r->m); mutex_unlock(&r->m); ... ...
Later, when a thread decides to free the memory pointed to by r, the thread should call mutex_destroy( ) on the mutexes in this memory.
In the following example, the main thread can do a thr_join( ) on both of the above threads. If there are no other threads using the memory in r, the main thread can now safely free r:
for (i = 0; i < 2; i++) thr_join(0, 0, 0); mutex_destroy(&r->m); /* first destroy mutex */ free(r); /* Then free memory */
If the mutex is not destroyed, the program could have memory leaks.
See attributes(5) for descriptions of the following attributes:
ATTRIBUTE TYPE | ATTRIBUTE VALUE |
---|---|
MT-Level | MT-Safe |
Currently, the only supported policy is SCHED_OTHER. In Solaris, under the SCHED_OTHER policy, there is no established order in which threads are unblocked.
In the current implementation of threads, mutex_lock(), mutex_unlock(), and mutex_trylock() do not validate the mutex type. Therefore, an uninitialized mutex or a mutex with an invalid type does not return EINVAL. Interfaces for mutexes with an invalid type have unspecified behavior.
Uninitialized mutexes which are allocated locally may contain junk data. Such mutexes need to be initialized using mutex_init().
By default, if multiple threads are waiting for a mutex, the order of acquisition is undefined.
NAME | SYNOPSIS | DESCRIPTION | RETURN VALUES | ERRORS | EXAMPLES | ATTRIBUTES | SEE ALSO | NOTES