Methods for linux thread synchronization
Here is an example of thread insecure:
#include<> #include<> int ticket_num=10000000; void *sell_ticket(void *arg) { while(ticket_num>0) { ticket_num--; } } int main() { pthread_t t1,t2,t3; pthread_create(&t1, NULL, &sell_ticket, NULL); pthread_create(&t2, NULL, &sell_ticket, NULL); pthread_create(&t3, NULL, &sell_ticket, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); printf("ticket_num=%d\n", ticket_num); return 0; }
The operation results are as follows:
# gcc no_lock_demo.c -o no_lock_demo.out -pthread
# ./no_lock_demo.out
ticket_num=-2
The final result is not fixed, it may be 0 or -1. If this ticket_num variable represents inventory, then the inventory will be negative, so thread synchronization needs to be introduced to ensure thread safety.
Linux provides a variety of ways to handle thread synchronization, the most commonly used are mutex locks, spin locks, and semaphores.
Mutex lock
A mutex is essentially a special global variable, with two states: lock and unlock. The unlocked mutex can be obtained by a certain thread. When the mutex is held by a certain thread, the mutex will lock and become locked. After that, only the thread has the right to open the lock. Other threads that want to obtain the mutex will block until the mutex is unlocked.
Types of mutex:
Normal lock (PTHREAD_MUTEX_NORMAL): The default type of mutex lock. When a thread locks a normal lock, the remaining threads requesting the lock will form a waiting queue and obtain it according to priority after the lock is unlocked. This lock type ensures fairness in resource allocation. If a thread locks a normal lock that has been locked again, it will cause a deadlock; unlocking a normal lock that has been locked by other threads, or unlocking a normal lock that has been unlocked again, will lead to unpredictable consequences.
Error detection lock (PTHREAD_MUTEX_ERRORCHECK): If a thread locks another lock to an error detection lock that has been locked, the locking operation returns EDEADLK; if an error detection lock that has been locked by other threads or a unlocked error detection lock again, the unlocking operation returns EPERM.
Nested lock (PTHREAD_MUTEX_RECURSIVE): This lock allows a thread to lock it multiple times before releasing it without deadlocking; to obtain this lock, the owner of the current lock must perform multiple unlocking operations; to unlock a nested lock that has been locked by other threads, or to unlock an unlocked nested lock again, the unlocking operation returns to EPERM.
Default lock (PTHREAD_MUTEX_ DEFAULT): If a thread locks a locked default lock again, or unlocks a default lock that has been locked by other threads, or unlocks an unlocked default lock, it will lead to unexpected consequences; when this lock is implemented, it may be mapped into one of the three locks mentioned above.
Related methods:
// Create mutex locks in a static waypthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // Create a mutex lock dynamically, where the parameter mutexattr is used to specify the type of the mutex lock. See the above four types. If it is NULL, it is a normal lock.int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr); int pthread_mutex_lock(pthread_mutex_t *mutex); // Lock, blockint pthread_mutex_trylock(pthread_mutex_t *mutex); // Try to add locking, non-blockingint pthread_mutex_unlock(pthread_mutex_t *mutex); // Unlock
example:
#include<> #include<> int ticket_num=10000000; pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; void *sell_ticket(void *arg) { while(ticket_num>0) { pthread_mutex_lock(&mutex); if(ticket_num>0) { ticket_num--; } pthread_mutex_unlock(&mutex); } } int main() { pthread_t t1,t2,t3; pthread_create(&t1, NULL, &sell_ticket, NULL); pthread_create(&t2, NULL, &sell_ticket, NULL); pthread_create(&t3, NULL, &sell_ticket, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); printf("ticket_num=%d\n", ticket_num); return 0; }
Spin lock
As the name suggests, spin lock is a dead loop, which is constantly polling. When a thread does not obtain a spin lock, it will not enter a blocking and sleeping state like a mutex lock, but will continue to poll and acquire the lock. If the spin lock can be released quickly, then the performance will be very high. If the spin lock cannot be released for a long time, and there are even a large number of IO blocking in it, it will cause other threads that acquire the lock to be polled all the time, resulting in the CPU usage rate reaching 100%, especially the CPU time.
Related methods:
int pthread_spin_init(pthread_spinlock_t *lock, int pshared); // Create a spin lock int pthread_spin_lock(pthread_spinlock_t *lock); // Lock, blockint pthread_spin_trylock(pthread_spinlock_t *lock); // Try to add locking, non-blockingint pthread_spin_unlock(pthread_spinlock_t *lock); // Unlock
example:
#include<> #include<> int ticket_num=10000000; pthread_spinlock_t spinlock; void *sell_ticket(void *arg) { while(ticket_num>0) { pthread_spin_lock(&spinlock); if(ticket_num>0) { ticket_num--; } pthread_spin_unlock(&spinlock); } } int main() { pthread_spin_init(&spinlock, 0); pthread_t t1,t2,t3; pthread_create(&t1, NULL, &sell_ticket, NULL); pthread_create(&t2, NULL, &sell_ticket, NULL); pthread_create(&t3, NULL, &sell_ticket, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); printf("ticket_num=%d\n", ticket_num); return 0; }
Semaphore
A semaphore is a counter that controls the number of threads accessing finite shared resources.
Related methods:
// Create semaphore// pshared: generally takes 0, indicating the semaphore of the calling process. Non-0 means that the semaphore can share memory and is shared by multiple processes (Linux does not support it yet).// value: The initial value of the semaphore, the number of threads that can be accessed concurrently.int sem_init (sem_t* sem, int pshared, unsigned int value); int sem_wait (sem_t* sem); // Decrease the semaphore by 1, and the semaphore will block when it is 0 int sem_trywait (sem_t* sem); // The semaphore is reduced by 1, and the semaphore is 0 returns -1, and does not block int sem_timedwait (sem_t* sem, const struct timespec* abs_timeout); // Decrease the semaphore by 1, block when the semaphore is 0, until abs_timeout timeout returns -1 int sem_post (sem_t* sem); // Add 1 to the semaphore
example:
#include<> #include<> #include <> int ticket_num=10000000; sem_t sem; void *sell_ticket(void *arg) { while(ticket_num>0) { sem_wait(&sem); if(ticket_num>0) { ticket_num--; } sem_post(&sem); } } int main() { sem_init(&sem, 0, 1); // value=1 means that up to 1 thread accesses shared resources at the same time, which is equivalent to a mutex pthread_t t1,t2,t3; pthread_create(&t1, NULL, &sell_ticket, NULL); pthread_create(&t2, NULL, &sell_ticket, NULL); pthread_create(&t3, NULL, &sell_ticket, NULL); pthread_join(t1, NULL); pthread_join(t2, NULL); pthread_join(t3, NULL); printf("ticket_num=%d\n", ticket_num); return 0; }
Conditional variables
Condition variables allow the calling thread to run when a specific condition is met. When the condition is not met, the blocking waits for wake-up and must be used in conjunction with the mutex.
Conditional variables are often used in producer and consumer models.
Related methods:
pthread_cond_t cond=PTHREAD_COND_INITIALIZER; // Create a condition variable, a mutex can correspond to multiple condition variables int pthread_cond_wait (pthread_cond_t* cond,pthread_mutex_t* mutex); // Blocking and waiting for the conditions to be met, and releasing the mutex lock mutex int pthread_cond_timedwait (pthread_cond_t* cond, pthread_mutex_t* mutex, const struct timespec* abstime); // Blocking with timeout waits for the condition to be met, and releases the mutex lock mutex at the same time // Call out a thread from the condition variable cond to regain the original mutex lock// The called out thread will be returned from the pthread_cond_wait function at this moment, but if the thread cannot obtain the original lock, it will continue to block on the lock.int pthread_cond_signal (pthread_cond_t* cond); // Call out all threads from the condition variable condint pthread_cond_broadcast (pthread_cond_t* cond);
example:
#include<> #include<> int max_buffer=10; int count=0; pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER; pthread_cond_t notempty=PTHREAD_COND_INITIALIZER; pthread_cond_t notfull=PTHREAD_COND_INITIALIZER; void *produce(void *args) { while(1) { pthread_mutex_lock(&mutex); while(count == max_buffer) { printf("buffer is full, wait...\n"); pthread_cond_wait(¬full, &mutex); } printf("produce ...\n"); count++; sleep(1); pthread_cond_signal(¬empty); pthread_mutex_unlock(&mutex); } } void *consumer(void *args) { while(1) { pthread_mutex_lock(&mutex); while(count == 0) { printf("buffer is empty, wait...\n"); pthread_cond_wait(¬empty, &mutex); } printf("consumer ...\n"); count--; sleep(1); pthread_cond_signal(¬full); pthread_mutex_unlock(&mutex); } } int main() { pthread_t t1,t2,t3,t4; pthread_create(&t1, NULL, &produce, NULL); pthread_create(&t2, NULL, &produce, NULL); pthread_create(&t3, NULL, &consumer, NULL); pthread_create(&t4, NULL, &consumer, NULL); pthread_join(t1, NULL); return 0; }
Read and write lock
There are three states for read and write locks: locked state in read mode, locked state in write mode, and no locked state. Only one thread can occupy the read and write lock in the write mode at a time, but multiple threads can occupy the read and write lock in the read mode at the same time. Read and write lock is also called a sharing-exclusive lock. When the read and write lock is locked in read mode, it is locked in shared mode. When it is locked in write mode, it is locked in exclusive mode, read and share, read and write mutually exclusive.
Related methods:
// Create a read and write lockpthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER; int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // Add read lock, blockint pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // Add a write lock, blockint pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // Release the read lock or write lock int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); // Try to add a read lock, non-blockingint pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); // Try to add a write lock, non-blocking
example:
#include <> #include <> pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER; void *read(void *arg) { while(1) { pthread_rwlock_rdlock(&rwlock); rintf("read message.\n"); sleep(1); pthread_rwlock_unlock(&rwlock); sleep(1); } } void *write(void *arg) { while(1) { pthread_rwlock_wrlock(&rwlock); printf("write message.\n"); sleep(1); pthread_rwlock_unlock(&rwlock); sleep(1); } } int main(int argc,char *argv[]) { pthread_t t1,t2,t3; pthread_create(&t1, NULL, &read, NULL); pthread_create(&t2, NULL, &read, NULL); pthread_create(&t3, NULL, &write, NULL); pthread_join(t1, NULL); return 0; }
barrier
A barrier is a synchronization mechanism for users to coordinate the parallel work of multiple threads. The barrier allows each thread to wait until all cooperating threads reach a certain point, and then all threads continue to execute from that point. The pthread_join function is a barrier that allows one thread to wait until another thread exits. But the concept of barrier objects is broader, allowing any number of threads to wait until all threads complete the processing work, and the threads do not need to exit, and can continue to work after all threads reach the barrier.
Related methods:
// Create barriersint pthread_barrier_init(pthread_barrier_t *barrier,const pthread_barrrierattr_t *attr,unsigned int count) // Block and wait until all threads arriveint pthread_barrier_wait(pthread_barrier_t *barrier)
example:
#include <> #include <> pthread_barrier_t barrier; void *go(void *arg){ sleep (rand () % 10); printf("%lu is arrived.\n", pthread_self()); pthread_barrier_wait(&barrier); printf("%lu go shopping...\n", pthread_self()); } int main() { pthread_barrier_init(&barrier, NULL, 3); pthread_t t1,t2,t3; pthread_create(&t1, NULL, &go, NULL); pthread_create(&t2, NULL, &go, NULL); pthread_create(&t3, NULL, &go, NULL); pthread_join(t1, NULL); return 0; }
The above is the detailed content of 6 methods to implement thread synchronization in Linux. For more information about Linux thread synchronization, please pay attention to my other related articles!