Home  >  Article  >  Operation and Maintenance  >  There are several ways to implement thread synchronization in Linux

There are several ways to implement thread synchronization in Linux

青灯夜游
青灯夜游Original
2022-07-01 19:39:234671browse

6 ways: 1. Mutex lock, which is essentially a special global variable with two states of lock and unlock; 2. Spin lock, is an infinite loop, non-stop polling; 3 , Semaphores, used to control the number of threads accessing limited shared resources; 4. Condition variables, which allow the calling thread to run when certain conditions are met, and block and wait to be awakened when the conditions are not met; 5. Read-write locks, which can only be awakened at a time There can be one thread that can occupy the read-write lock in write mode; 6. Barrier is a synchronization mechanism for users to coordinate the parallel work of multiple threads.

There are several ways to implement thread synchronization in Linux

#The operating environment of this tutorial: linux7.3 system, Dell G3 computer.

6 ways to achieve thread synchronization in Linux

The following is an example of thread unsafety:

#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 running 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 of the operation is not fixed and may be 0 or -1. If the ticket_num variable represents the 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

The essence of the mutex lock is a special global variable, which has two states: lock and unlock. The unlock mutex lock can be controlled by a thread. Obtained, when the mutex is held by a thread, the mutex will be locked and turned into the lock state. After that, only the thread has the power to open the lock, and other threads that want to obtain the mutex will be blocked until The mutex is unlocked.

Type of mutex lock:

  • Normal lock (PTHREAD_MUTEX_NORMAL): Default type of mutex lock. When a thread locks a common lock, the remaining threads requesting the lock will form a waiting queue and obtain the lock according to priority after it is unlocked. This lock type ensures fairness in resource allocation. If a thread locks an ordinary lock that has been locked again, it will cause a deadlock; unlocking an ordinary lock that has been locked by another thread, or unlocking an ordinary lock that has been unlocked again, will lead to unpredictable consequences. .

  • Error-checking lock (PTHREAD_MUTEX_ERRORCHECK): If a thread locks an already-locked error-checking lock again, the locking operation returns EDEADLK; for a thread that has been locked by another thread If the error detection lock is unlocked or an already unlocked error detection lock is unlocked again, the unlocking operation returns to EPERM.

  • Nested lock (PTHREAD_MUTEX_RECURSIVE): This lock allows a thread to lock it multiple times before releasing the lock without deadlock; if other threads want to obtain this lock, the current lock The owner must perform multiple unlocking operations; to unlock a nested lock that has been locked by another thread, or to unlock an already unlocked nested lock again, the unlocking operation returns EPERM.

  • Default lock (PTHREAD_MUTEX_DEFAULT): If a thread locks an already locked default lock again, or unlocks a default lock that has been locked by another thread, or Unlocking an unlocked default lock will lead to unpredictable consequences; this lock may be mapped to one of the above three locks when implemented.

Related methods:

// 静态方式创建互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 

// 动态方式创建互斥锁,其中参数mutexattr用于指定互斥锁的类型,具体类型见上面四种,如果为NULL,就是普通锁。
int pthread_mutex_init (pthread_mutex_t* mutex,const pthread_mutexattr_t* mutexattr);

int pthread_mutex_lock(pthread_mutex_t *mutex); // 加锁,阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 尝试加锁,非阻塞
int pthread_mutex_unlock(pthread_mutex_t *mutex); // 解锁

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

Spin lock is as the name suggests It is an infinite loop, constantly polling. When a thread does not obtain the spin lock, it will not enter the blocking sleep state like a mutex lock, but will continuously poll to acquire the lock. If the spin lock can be quickly If the spin lock is released, the performance will be very high. If the spin lock cannot be released for a long time, or there is even a large amount of IO blocking, it will cause other threads that acquire the lock to poll continuously, causing the CPU usage to reach 100%. , specifically CPU time.

Related methods:

int pthread_spin_init(pthread_spinlock_t *lock, int pshared); // 创建自旋锁

int pthread_spin_lock(pthread_spinlock_t *lock); // 加锁,阻塞
int pthread_spin_trylock(pthread_spinlock_t *lock); // 尝试加锁,非阻塞
int pthread_spin_unlock(pthread_spinlock_t *lock); // 解锁

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

The semaphore is a counter used to control limited access The number of threads sharing the resource.

Related methods:

// 创建信号量
// pshared:一般取0,表示调用进程的信号量。非0表示该信号量可以共享内存的方式,为多个进程所共享(Linux暂不支持)。
// value:信号量的初始值,可以并发访问的线程数。
int sem_init (sem_t* sem, int pshared, unsigned int value);

int sem_wait (sem_t* sem); // 信号量减1,信号量为0时就会阻塞

int sem_trywait (sem_t* sem); // 信号量减1,信号量为0时返回-1,不阻塞

int sem_timedwait (sem_t* sem, const struct timespec* abs_timeout); // 信号量减1,信号量为0时阻塞,直到abs_timeout超时返回-1

int sem_post (sem_t* sem); // 信号量加1

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表示最多1个线程同时访问共享资源,与互斥量等价
    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;
}

Condition variable

Condition variable allows the calling thread to wait until specific conditions are met. It runs under certain conditions, blocks and waits to be awakened when the conditions are not met, and must be used in conjunction with a mutex lock.

Conditional variables are often used in producer and consumer models.

Related methods:

pthread_cond_t cond=PTHREAD_COND_INITIALIZER; // 创建条件变量,一个互斥锁可以对应多个条件变量

int pthread_cond_wait (pthread_cond_t* cond,pthread_mutex_t* mutex); // 阻塞等待条件满足,同时释放互斥锁mutex

int pthread_cond_timedwait (pthread_cond_t* cond,
    pthread_mutex_t* mutex,
    const struct timespec* abstime); // 带超时的阻塞等待条件满足,同时释放互斥锁mutex

// 从条件变量cond中唤出一个线程,令其重新获得原先的互斥锁
// 被唤出的线程此刻将从pthread_cond_wait函数中返回,但如果该线程无法获得原先的锁,则会继续阻塞在加锁上。
int pthread_cond_signal (pthread_cond_t* cond);

// 从条件变量cond中唤出所有线程
int 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-write lock

Read-write lock can have three states: read mode The locked state is in the write mode, the locked state is in the write mode, and the unlocked state is in the write mode. Only one thread can hold a read-write lock in write mode at a time, but multiple threads can hold a read-write lock in read mode at the same time. Read-write lock is also called shared-exclusive lock. When the read-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-write lock Shared, read and write mutually exclusive.

Only one thread can occupy the read-write lock in write mode at a time, but multiple threads can simultaneously hold the read-write lock in read mode. Therefore read-write locks allow higher parallelism compared to mutexes. Read-write locks are very suitable for situations where the number of reads to the data structure is much greater than the number of writes.

Related methods:

// 创建读写锁
pthread_rwlock_t rwlock=PTHREAD_RWLOCK_INITIALIZER;

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 加读锁,阻塞
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 加写锁,阻塞
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 释放读锁或者写锁

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); // 尝试加读锁,非阻塞
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); // 尝试加写锁,非阻塞

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)是用户协调多个线程并行工作的同步机制。屏障允许每个线程等待,直到所有的合作线程都到达某一点,然后所有线程都从该点继续执行。pthread_join函数就是一种屏障,允许一个线程等待,直到另一个线程退出。但屏障对象的概念更广,允许任意数量的线程等待,直到所有的线程完成处理工作,而线程不需要退出,当所有的线程达到屏障后可以接着工作。

相关方法:

// 创建屏障
int pthread_barrier_init(pthread_barrier_t *barrier,const pthread_barrrierattr_t *attr,unsigned int count)

// 阻塞等待,直到所有线程都到达
int pthread_barrier_wait(pthread_barrier_t *barrier)

例子:

#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;
}

相关推荐:《Linux视频教程

The above is the detailed content of There are several ways to implement thread synchronization in Linux. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn