线程同步的概念

线程同步指的是
当一个线程在对某个临界资源进行操作时,其他线程都不可以对这个资源进行操作,直到该线程完成操作,其他线程才能操作,也就是协同步调,让线程按预定的先后次序进行运行。

线程同步的方法

线程同步的方法有四种:互斥锁、信号量、条件变量、读写锁。

1.互斥锁

互斥锁(也称互斥量)可以用于保护关键代码段,以确保其独占式的访问。当进入关键代码段时,我们需要获得互斥锁并将其加锁;当离开关键代码段时,我们需要对互斥锁解锁,以唤醒其他等待该互斥锁的线程。

基础API

互斥锁的主要函数主要有以下五个:

#include<pthread.h>
//初始化互斥锁
int pthread_mutex_init(pthread_mutex_t* mutex, pthread_mutexattr_t* mutexattr);

//加锁
int pthread_mutex_lock(pthread_mutex_t* mutex);

//解锁
int pthread_mutex_unlock(pthread_mutex_t* mutex);

//销毁
int pthread_mutex_destroy(pthread_mutex_t* mutex);

int pthread_mutex_trylock(pthread_mutex_t* mutex);

参数介绍:

  1. mutex : 指向要操作的目标互斥锁,互斥锁的类型是 pthread_mutex_t的结构体。
  2. mutexattr :指定互斥锁的属性。若设置为NULL, 则表示使用默认属性。

使用示例

主线程和函数线程模拟访问打印机,主线程输出第一个字符‘a’表示开始使用打印机,输出第二个字符‘a’表示结束使用,函数线程操作与主线程相同。(由于打印机同一时刻只能被一个线程使用,所以输出结果不应该出现 abab)。

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>

pthread_mutex_t mutex;

void* thread_fun(void* arg)
{
    int i = 0;
    for (; i < 5; ++i)
    {
        pthread_mutex_lock(&mutex);
        write(1, "B", 1);
        int n = rand() % 3;
        sleep(n);
        write(1, "B", 1);
        pthread_mutex_unlock(&mutex);
        n = rand() % 3;
        sleep(n);
    }

    pthread_exit(NULL);
}

int main(void)
{
    pthread_t id;

    pthread_mutex_init(&mutex, NULL);
    pthread_create(&id, NULL, thread_fun, NULL);

    int i = 0;
    for (; i < 5; ++i)
    {
        pthread_mutex_lock(&mutex);
        write(1, "A", 1);
        int n = rand() % 3;
        sleep(n);
        write(1, "A", 1);
        pthread_mutex_unlock(&mutex);
        n = rand() % 3;
        sleep(n);
    }

    pthread_join(id, NULL);
    pthread_mutex_destroy(&mutex);

    exit(0);
}

运行结果:
在这里插入图片描述

2.信号量

POSIX信号量函数的名字都以sem_开头,并不像大多数线程函数那样以pthread_开头。
常用的POSIX信号量函数是下面5个:

基础API

#include <semaphore.h>

int sem_init(sem_t* sem, int pshaared, unsigned int value);

int sem_wait(sem_t* sem);

int sem_post(sem_t* sem);

int sem_destroy(sem_t* sem);

int sem_trywait(sem_t* sem);

sem_wait函数以原子操作的方式将信号量的值减1, 如果信号量的值为0,那sem_wait将被阻塞, 直到这个信号量具有非0值。

sem_post函数以原子操作的方式将信号量的值加1, 当信号量的值大于0时, 其他正在调用sem_wait等待信号量的线程将被唤醒。

上面这些函数成功时返回0, 失败返回-1并设置errno。

参数介绍:

  1. sem 指向被操作的信号量。
  2. pshared参数治党信号量的类型。如果其值为0,就表示这个信号量是当前进程的局部信号量,否则该信号量就可以在多个进程之间共享。
  3. value参数指定信号量的初始值。

示例

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
#include<semaphore.h>
#include<fcntl.h>
#include<assert.h>
#include<string.h>

char buff[128] = {0};

sem_t sem1;
sem_t sem2;

void* PthreadFun(void* arg)
{
    int fd = open("a.txt", O_RDWR | O_CREAT, 0664);
    assert(fd != -1);

    //函数线程完成将用户输入的数据存储到文件中
    while (1)
    {
        sem_wait(&sem2);

        if (strncmp(buff, "end", 3) == 0)
        {
            break;
        }

        write(fd, buff, strlen(buff));
        memset(buff, 0, 128);

        sem_post(&sem1);
    }
    
    sem_destroy(&sem1);
    sem_destroy(&sem2);
}

int main(void)
{
    sem_init(&sem1, 0, 1);
    sem_init(&sem2, 0, 0);

    pthread_t id;

    int res = pthread_create(&id, NULL, PthreadFun, NULL);
    assert(res == 0);

    //主线程完成获取用户数据的数据,并存储在全局数组 buff 中
    while (1)
    {
        sem_wait(&sem1);

        printf("please input data: ");
        fflush(stdout);

        fgets(buff, 128, stdin);
        buff[strlen(buff) - 1] = 0;

        sem_post(&sem2);

        if (strncmp(buff, "end", 3) == 0)
        {
            break;
        }
    }

    pthread_exit(NULL);
}

运行结果:

在这里插入图片描述

3.条件变量

如果说互斥锁是用于同步线程对共享数据的访问的话,那么条件变量则是用于在线程之间同步共享数据的值。
条件变量提供了一种线程间的通知机制:当某个共享数据达到某个值的时候,唤醒等待这个共享数据的线程。

基础API

#include<pthread.h>

int pthread_cond_init(pthread_cond_t* cond, pthread_condattr_t* attr);

int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);

//唤醒单个线程
int pthread_cond_signal(pthread_cond_t* cond); 

//唤醒所有等待的线程
int pthread_cond_broadcast(pthread_cond_t* cond); 

int pthread_cond_destroy(pthread_cond_t *cond);

这些函数成功时返回0,失败则返回错误码。

参数介绍:

  1. cond指向要操作的目标条件变量, 类型是pthread_cond_t结构体。
  2. cond_attr参数指定条件变量的属性,如果将它设置为NULL, 则表示使用默认属性。

pthread_cond_wait 函数用于等待目标条件变量。mutex 参数是用于保护条件变量的互斥锁,以确保pthread_cond_wait操作的原子性。在调用pthread_cond_wait前,必须确保互斥锁mutex已经加锁,否则将导致不可预期的结果。pthread_cond_wait 函数执行时,首先把调用线程放人条件变量的等待队列中,然后将互斥锁mutex解锁。可见,从pthread_cond_wait开始执行到其调用线程被放入条件变量的等待队列之间的这段时间内,pthread_cond_signal和pthread_cond_broadcast等函数不会修改条件变量。换言之,pthread_cond_wait 函数不会错过目标条件变量的任何变化。当pthread_cond_wait 函数成功返回时,互斥锁mutex将再次被锁上。

使用示例

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>

pthread_mutex_t mutex;
pthread_cond_t cond;

void* fun1(void* arg)
{
    char* s = (char*) arg;
    while (1)
    {
        //阻塞,被唤醒
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);
        pthread_mutex_unlock(&mutex);

        printf("fun1 read: %s\n", s);

        if (strncmp(s, "emd", 3) == 0)
        {
            break;
        }
    }
}

void* fun2(void* arg)
{
    char* s = (char*)arg;

    while (1)
    {
        //阻塞,被唤醒
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond, &mutex);   
        pthread_mutex_unlock(&mutex);

        printf("fun2 read: %s\n", s);

        if (strncmp(s, "end", 3) == 0)
        {
            break;
        }
    }
}

int main(void)
{
    pthread_t id[2];
    char buff[128] = {0};

    pthread_cond_init(&cond, NULL);
    pthread_mutex_init(&mutex, NULL);
    pthread_create(&id[0], NULL, fun1, (void*)buff);
    pthread_create(&id[1], NULL, fun2, (void*)buff);

    while (1)
    {
        fgets(buff, 128, stdin);

        if (strncmp(buff, "end", 3) == 0)
        {
            pthread_mutex_lock(&mutex);
            pthread_cond_broadcast(&cond);
            pthread_mutex_unlock(&mutex);

            break;
        }
        else
        {
            pthread_mutex_lock(&mutex);
            pthread_cond_signal(&cond);
            pthread_mutex_unlock(&mutex);
        }
    }

    pthread_join(id[0], NULL);
    pthread_join(id[1], NULL);
    
    exit(0);
}


运行示例:
在这里插入图片描述

4.读写锁

基础API

#include<pthread.h>

int pthread_rwlock_init(pthread_rwlock_t *rwlock, pthread_rwlockattr_t *attr);

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_destroy(pthread_rwlock_t *rwlock);

end

参考资料

《Linux高性能服务器编程》
《linux平台开发教程》
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐