【Linux系统编程】线程之间的同步与协调

这里介绍一下如何使用线程来实现并发的功能,如何使用互斥锁或者信号量来实现线程同步,如何使用条件变量来实现多线程之间的通信,借助条件变量,可以实现线程之间的协调,使得各个线程能够按照特定的条件进行等待或唤醒。

目录

线程同步

互斥锁

信号量

线程协调通信

条件变量


线程同步

现在我们有两个线程,都给全局变量counter增加5000次

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

#define NLOOP 5000
int counter;

void *increase(void *vptr);

int main(int argc, char **argv) {
    pthread_t threadIdA, threadIdB;
    pthread_create(&threadIdA, NULL, &increase, NULL);
    pthread_create(&threadIdB, NULL, &increase, NULL);
    pthread_join(threadIdA, NULL);
    pthread_join(threadIdB, NULL);
    return 0;
}

void *increase(void *vptr) {
    int i, val;
    for (i = 0; i < NLOOP; i++) {
        val = counter;
        printf("%x: %d\n", (unsigned int) pthread_self(), val + 1);
        counter = val + 1;
    }
    return NULL;
}

 运行结果是这样的

 原程序会出现竟态条件,存在多个线程同时给共享变量赋值的情况,这会导致结果错误。

我们可以使用互斥锁或者信号量的同步机制来保证线程之间的同步,实际上,无论我们使用互斥锁还是信号量的处理方法,我们都会遇到一个问题,那就是究竟选择是在循环外加锁还是循环内加锁。

这种情况下,应该选择在循环内加锁。如果将锁放在循环外部,那么当一个线程获得锁并开始执行加法操作时,另一个线程必须等待,直到锁被释放,循环次数越多,线程间的等待就越久,并发性能严重下降。

互斥锁

互斥锁(Mutex)是一种用于多线程编程中的同步机制,用于保护共享资源,防止多个线程同时访问或修改同一资源而导致数据不一致或冲突。

互斥锁提供了两个基本操作:锁定(Lock)和解锁(Unlock)。当一个线程获得了互斥锁的锁定状态后,其他线程就无法立即获取该锁,只能等待锁被解锁后才能尝试获取。这样可以确保在任意时刻只有一个线程能够访问被保护的资源,从而避免了竞态条件和数据不一致的问题。

使用互斥锁时,需要在访问共享资源之前对互斥锁进行加锁操作,访问完毕后再进行解锁操作,这样可以保证在同一时间只有一个线程可以访问该资源。互斥锁可以防止多个线程同时修改共享资源,保证了线程安全性。

添加一个全局互斥锁,在主线程中初始化互斥锁,然后在操作完成后销毁互斥锁。

在每次对counter进行处理的时候都先加锁,在操作完成之后再解锁。

重新编译运行程序,可以得到想要的结果了。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

#define NLOOP 5000

int counter;
pthread_mutex_t mutex; // 互斥锁

void *increase(void *vptr) {
    int i, val;
    for (i = 0; i < NLOOP; i++) {
        pthread_mutex_lock(&mutex); // 加锁
        val = counter;
        printf("%x: %d\n", (unsigned int)pthread_self(), val + 1);
        counter = val + 1;
        pthread_mutex_unlock(&mutex); // 解锁
    }
    return NULL;
}

int main(int argc, char **argv) {
    pthread_t threadIdA, threadIdB;
    pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
    pthread_create(&threadIdA, NULL, &increase, NULL);
    pthread_create(&threadIdB, NULL, &increase, NULL);
    pthread_join(threadIdA, NULL);
    pthread_join(threadIdB, NULL);
    pthread_mutex_destroy(&mutex); // 销毁互斥锁
    return 0;
}

信号量

信号量(Semaphore)是一种用于多线程编程中的同步机制,用于控制对共享资源的访问。

信号量维护了一个计数器,用于表示可用的资源数量。当线程需要访问共享资源时,首先会尝试对信号量进行P操作(也称为申请操作),该操作会将信号量的计数器减1。如果计数器大于等于0,则表示有可用资源,线程可以继续执行;如果计数器小于0,则表示没有可用资源,线程需要等待。当线程使用完共享资源后,会对信号量进行V操作(也称为释放操作),该操作会将信号量的计数器加1,表示释放了一个资源。

添加一个全局的信号量,在主线程中初始化信号量,并在操作完成后销毁信号量。

     然后在每次线程对counter处理之前都先等待信号量,申请资源,然后操作完成之后再发送信号量释放资源。

重新编译运行程序,可以得到想要的结果了。

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

#define NLOOP 5000
int counter;

void *increase(void *vptr);

sem_t sem; // 信号量
int main(int argc, char **argv) {
    pthread_t threadIdA, threadIdB;
    sem_init(&sem, 0, 1); // 初始化信号量,初值为1
    pthread_create(&threadIdA, NULL, &increase, NULL);
    pthread_create(&threadIdB, NULL, &increase, NULL);
    pthread_join(threadIdA, NULL);
    pthread_join(threadIdB, NULL);
    sem_destroy(&sem); // 销毁信号量
    return 0;
}

void *increase(void *vptr) {
    int i, val;
    for (i = 0; i < NLOOP; i++) {
        sem_wait(&sem); // 等待信号量
        val = counter;
        printf("%x: %d\n", (unsigned int) pthread_self(), val + 1);
        counter = val + 1;
        sem_post(&sem); // 发送信号量
    }
    return NULL;
}

线程协调通信

条件变量

条件变量是一种用于多线程编程中的同步机制,通常与互斥锁结合使用,用于在线程间进行通信和协调。

条件变量主要用于线程的等待和通知。当一个线程在某个条件下无法继续执行时,可以通过条件变量将自己挂起,等待其他线程的通知。另外,当某个条件得到满足时,线程可以向其他线程发送通知,唤醒等待的线程继续执行。

假设需要开4个线程,这4个线程的ID分别为p1、p2、p3、p4(请以真实线程id代替),每个线程将自己的ID在屏幕上打印5遍,要求输出结果必须按p1*p2**p3***p4****的模式显示;即:p1*p2**p3***p4**** p1*p2**p3***p4****…依次递推。

这里我们就需要让这四个线程之间协调工作

我们这里使用到互斥锁和条件变量,先在声明的时候初始化,同时需要一个全局变量来控制每个线程的输出顺序。

主函数创建了四个线程,并向每个线程传入了需要打印*的次数参数,这里使用了一个times数组而不是times整型变量,这是因为防止线程还没使用到正确的times值之前times又在下一次的循环中被修改了。

在线程执行的函数中,先将指针转换为整型指针然后拿到整数的值,循环5次,每次循环中先加锁,然后判断counter和4取余是否等于打印*的次数减一,即判断是否轮到该线程输出,如果不是轮到该线程输出,那么该线程就进入等待。

    在某个线程输出完之后,counter++,同时唤醒所有等待线程并解锁。

编译运行程序,可见我们编写的程序输出了正确的结果。

#include <stdio.h>
#include <pthread.h>

int counter = 0;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

void *new(void *vptr) {
    int*p=(int*)vptr;
    int times=*p;
    for (int i = 0; i < 5; i++) {
        pthread_mutex_lock(&mutex); // 加锁
        while (counter%4 != times-1)
            pthread_cond_wait(&cond, &mutex);
        printf("%x", (unsigned int)pthread_self());
        counter++;
        for(int j=0;j<times;j++)
            putchar('*');
        if(times==4)
            putchar('\n');
        pthread_cond_broadcast(&cond);
        pthread_mutex_unlock(&mutex); // 解锁
    }
    return NULL;
}

int main(int argc, char **argv) {
    pthread_t threads[4];
    int times[4];
    for (int i = 0; i < 4; i++){
        times[i]=i+1;
        pthread_create(&threads[i], NULL, &new, &times[i]);
    }
    for (int i = 0; i < 4; i++)
        pthread_join(threads[i], NULL);
    pthread_mutex_destroy(&mutex); // 销毁互斥锁
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_62264287/article/details/134945914