【Linux多线程】三个经典同步问题

在了解了《同步与互斥的区别 》之后,我们来看看几个经典的线程同步的例子。相信通过具体场景可以让我们学会分析和解决这类线程同步的问题,以便以后应用在实际的项目中。

一、生产者-消费者问题

问题描述:

一组生产者进程和一组消费者进程共享一个初始为空、大小为 n 的缓冲区,只有缓冲区没满时,生产者才能把消息放入到缓冲区,否则必须等待;只有缓冲区不空时,消费者才能从中取出消息,否则必须等待。由于缓冲区是临界资源,它只允许一个生产者放入消息,或者一个消费者从中取出消息。

分析:

  1. 关系分析:生产者和消费者对缓冲区互斥访问是互斥关系,同时生产者和消费者又是一个相互协作的关系,只有生产者生产之后,消费者才能消费,它们也是同步关系。

  2. 整理思路:这里比较简单,只有生产者和消费者两个进程,且这两个进程存在着互斥关系和同步关系。那么需要解决的是互斥和同步的PV操作的位置。

  3. 信号量设置:信号量mutex作为互斥信号量,用于控制互斥访问缓冲池,初值为1;信号量full用于记录当前缓冲池中“满”缓冲区数,初值为 0;信号量empty用于记录当前缓冲池中“空”缓冲区数,初值为n。

代码示例:(semaphore类的封装见下文)

#include<iostream>
#include<unistd.h>  // sleep
#include<pthread.h>
#include"semaphore.h"
using namespace std;
#define N 5

semaphore mutex("/", 1);           // 临界区互斥信号量
semaphore empty("/home", N);       // 记录空缓冲区数,初值为N
semaphore full("/home/songlee",0); // 记录满缓冲区数,初值为0
int buffer[N];                     // 缓冲区,大小为N
int i=0;
int j=0;

void* producer(void* arg)
{
    empty.P();                 // empty减1
    mutex.P();

    buffer[i] = 10 + rand() % 90;
    printf("Producer %d write Buffer[%d]: %d\n",arg,i+1,buffer[i]);
    i = (i+1) % N;

    mutex.V();
    full.V();                  // full加1 
}

void* consumer(void* arg)
{
    full.P();                  // full减1
    mutex.P();

    printf("                               \033[1;31m");
    printf("Consumer %d read Buffer[%d]: %d\n",arg,j+1,buffer[j]);
    printf("\033[0m");
    j = (j+1) % N;

    mutex.V();
    empty.V();                 // empty加1
}


int main()
{
    pthread_t id[10];

    // 开10个生产者线程,10个消费者线程
    for(int k=0; k<10; ++k)
        pthread_create(&id[k], NULL, producer, (void*)(k+1));

    for(int k=0; k<10; ++k)
        pthread_create(&id[k], NULL, consumer, (void*)(k+1));

    sleep(1);
    return 0;
}

编译运行输出结果:

Producer 1 write Buffer[1]: 83
Producer 2 write Buffer[2]: 26
Producer 3 write Buffer[3]: 37
Producer 5 write Buffer[4]: 35
Producer 4 write Buffer[5]: 33
                               Consumer 1 read Buffer[1]: 83
Producer 6 write Buffer[1]: 35
                               Consumer 2 read Buffer[2]: 26
                               Consumer 3 read Buffer[3]: 37
                               Consumer 4 read Buffer[4]: 35
                               Consumer 5 read Buffer[5]: 33
                               Consumer 6 read Buffer[1]: 35
Producer 7 write Buffer[2]: 56
Producer 8 write Buffer[3]: 22
Producer 10 write Buffer[4]: 79
                               Consumer 9 read Buffer[2]: 56
                               Consumer 10 read Buffer[3]: 22
Producer 9 write Buffer[5]: 11
                               Consumer 7 read Buffer[4]: 79
                               Consumer 8 read Buffer[5]: 11


二、读者-写者问题

问题描述:

有读者和写者两组并发线程,共享一个文件,当两个或以上的读线程同时访问共享数据时不会产生副作用,但若某个写线程和其他线程(读线程或写线程)同时访问共享数据时则可能导致数据不一致的错误。因此要求:

  • 允许多个读者可以同时对文件执行读操作;
  • 只允许一个写者往文件中写信息;
  • 任一写者在完成写操作之前不允许其他读者或写者工作;
  • 写者执行写操作前,应让已有的读者和写者全部退出。

分析:

  1. 关系分析:由题目分析可知,读者和写者是互斥的,写者和写者也是互斥的,而读者和读者不存在互斥问题。

  2. 整理思路:写者是比较简单的,它与任何线程互斥,用互斥信号量的 PV 操作即可解决。读者的问题比较复杂,它必须实现与写者的互斥,多个读者还可以同时读。所以,在这里用到了一个计数器,用它来判断当前是否有读者读文件。当有读者的时候写者是无法写文件的,此时读者会一直占用文件,当没有读者的时候写者才可以写文件。同时,不同的读者对计数器的访问也应该是互斥的。

  3. 信号量设置:首先设置一个计数器count,用来记录当前的读者数量,初值为0;设置互斥信号量mutex,用于保护更新 count 变量时的互斥;设置互斥信号量rw用于保证读者和写者的互斥访问。

代码示例:

#include<iostream>
#include<unistd.h>  // sleep
#include<pthread.h>
#include"semaphore.h"
using namespace std;

int count = 0;           // 记录当前的读者数量
semaphore mutex("/",1);  // 用于保护更新count变量时的互斥
semaphore rw("/home",1); // 用于保证读者和写者的互斥

void* writer(void* arg)
{
    rw.P();              // 互斥访问共享文件

    printf("  Writer %d start writing...\n", arg);
    sleep(1);
    printf("  Writer %d finish writing...\n", arg);

    rw.V();              // 释放共享文件
}

void* reader(void* arg)
{
    mutex.P();           // 互斥访问count变量
    if(count == 0)       // 当第一个读线程读文件时
        rw.P();          // 阻止写线程写
    ++count;             // 读者计数器加1
    mutex.V();           // 释放count变量

    printf("Reader %d start reading...\n", arg);
    sleep(1);
    printf("Reader %d finish reading...\n", arg);

    mutex.P();           // 互斥访问count变量
    --count;             // 读者计数器减1
    if(count == 0)       // 当最后一个读线程读完文件
        rw.V();          // 允许写线程写
    mutex.V();           // 释放count变量
}


int main()
{
    pthread_t id[8];     // 开6个读线程,2个写线程

    pthread_create(&id[0], NULL, reader, (void*)1);
    pthread_create(&id[1], NULL, reader, (void*)2);

    pthread_create(&id[2], NULL, writer, (void*)1);
    pthread_create(&id[3], NULL, writer, (void*)2);

    pthread_create(&id[4], NULL, reader, (void*)3);
    pthread_create(&id[5], NULL ,reader, (void*)4);
    sleep(2);
    pthread_create(&id[6], NULL, reader, (void*)5);
    pthread_create(&id[7], NULL ,reader, (void*)6);

    sleep(4);
    return 0;
}

编译运行的结果如下:

Reader 2 start reading...
Reader 1 start reading...
Reader 3 start reading...
Reader 4 start reading...
Reader 1 finish reading...
Reader 2 finish reading...
Reader 3 finish reading...
Reader 4 finish reading...
  Writer 1 start writing...
  Writer 1 finish writing...
  Writer 2 start writing...
  Writer 2 finish writing...
Reader 5 start reading...
Reader 6 start reading...
Reader 5 finish reading...
Reader 6 finish reading...


三、哲学家进餐问题

问题描述:

一张圆桌上坐着 5 名哲学家,桌子上每两个哲学家之间摆了一根筷子,桌子的中间是一碗米饭,如图所示:



哲学家们倾注毕生精力用于思考和进餐,哲学家在思考时,并不影响他人。只有当哲学家饥饿的时候,才试图拿起左、右两根筷子(一根一根拿起)。如果筷子已在他人手上,则需等待。饥饿的哲学家只有同时拿到了两根筷子才可以开始进餐,当进餐完毕后,放下筷子继续思考。

分析:

  1. 关系分析:5名哲学家与左右邻居对其中间筷子的访问是互斥关系。

  2. 整理思路:显然这里有 5 个线程,那么要如何让一个哲学家拿到左右两个筷子而不造成死锁或饥饿现象?解决方法有两个,一个是让他们同时拿两个筷子;二是对每个哲学家的动作制定规则,避免饥饿或死锁现象的发生。

  3. 信号量设置:定义互斥信号量数组chopstick[5] = {1,1,1,1,1}用于对 5 根筷子的互斥访问。

示例代码:

semaphore chopstick[5] = {1,1,1,1,1}  // 信号量数组
Pi()                                  // i号哲学家的线程
{
    do
    {
        P(chopstick[i]);              // 取左边筷子
        P(chopstick[(i+1)%5]);        // 取右边筷子
        eat;                          // 进餐
        V(chopstick[i]);              // 放回左边筷子
        V(chopstick[(i+1)%5]);        // 放回右边筷子
        think;                        // 思考
    }while(1);
}

上面的伪代码存在一个问题:当五个哲学家都想要进餐,分别拿起他们左边筷子的时候(都恰好执行完P(chopstick[i])),筷子已经被拿光了,等到他们再想拿右边的筷子的时候,就全被阻塞了,这就出现了死锁。

为了防止死锁的发生,可以对哲学家线程施加一些限制条件,比如:

  • 至多允许四个哲学家同时进餐;
  • 仅当一个哲学家左右两边的筷子都可用时才允许他抓起筷子;
  • 对哲学家顺序编号,要求奇数号哲学家先抓左边的筷子,然后再抓他右边的筷子,而偶数号哲学家刚好相反。

这里,我们采用第二种方法来改进上面的算法,即当一个哲学家左右两边的筷子都可用时,才允许他抓起筷子。

#include<iostream>
#include<vector>
#include<unistd.h>  // sleep
#include<pthread.h>
#include"semaphore.h"
using namespace std;

vector<semaphore*> chopstick;   // 信号量数组
semaphore mutex("/", 1);  // 设置取左右筷子的信号量 <-- 关键

void* P1(void* arg)  // 第1个哲学家线程
{
    mutex.P();                 // 在取筷子前获得互斥量
    chopstick[0]->P();         // 取左边筷子
    chopstick[1]->P();         // 取右边筷子
    mutex.V();                 // 释放取筷子的信号量

    printf("Philosopher 1 eat.\n");

    chopstick[0]->V();          // 放回左边筷子
    chopstick[1]->V();          // 放回右边筷子
}

void* P2(void* arg)  // 第2个哲学家线程
{
    mutex.P();                 // 在取筷子前获得互斥量
    chopstick[1]->P();         // 取左边筷子
    chopstick[2]->P();         // 取右边筷子
    mutex.V();                 // 释放取筷子的信号量

    printf("Philosopher 2 eat.\n");

    chopstick[1]->V();          // 放回左边筷子
    chopstick[2]->V();          // 放回右边筷子
}


void* P3(void* arg)  // 第3个哲学家线程
{
    mutex.P();                 // 在取筷子前获得互斥量
    chopstick[2]->P();         // 取左边筷子
    chopstick[3]->P();         // 取右边筷子
    mutex.V();                 // 释放取筷子的信号量

    printf("Philosopher 3 eat.\n");

    chopstick[2]->V();          // 放回左边筷子
    chopstick[3]->V();          // 放回右边筷子
}

void* P4(void* arg)  // 第4个哲学家线程
{
    mutex.P();                 // 在取筷子前获得互斥量
    chopstick[3]->P();         // 取左边筷子
    chopstick[4]->P();         // 取右边筷子
    mutex.V();                 // 释放取筷子的信号量

    printf("Philosopher 4 eat.\n");

    chopstick[3]->V();          // 放回左边筷子
    chopstick[4]->V();          // 放回右边筷子
}


void* P5(void* arg)  // 第5个哲学家线程
{
    mutex.P();                 // 在取筷子前获得互斥量
    chopstick[4]->P();         // 取左边筷子
    chopstick[0]->P();         // 取右边筷子
    mutex.V();                 // 释放取筷子的信号量

    printf("Philosopher 5 eat.\n");

    chopstick[4]->V();          // 放回左边筷子
    chopstick[0]->V();          // 放回右边筷子
}

int main()
{
    semaphore *sem1 = new semaphore("/home", 1);
    semaphore *sem2 = new semaphore("/home/songlee", 1);
    semaphore *sem3 = new semaphore("/home/songlee/java", 1);
    semaphore *sem4 = new semaphore("/home/songlee/ADT", 1);
    semaphore *sem5 = new semaphore("/home/songlee/Test", 1);
    chopstick.push_back(sem1);
    chopstick.push_back(sem2);
    chopstick.push_back(sem3);
    chopstick.push_back(sem4);
    chopstick.push_back(sem5);

    pthread_t id;

    pthread_create(&id, NULL, P1, NULL);
    pthread_create(&id, NULL, P2, NULL);
    pthread_create(&id, NULL, P3, NULL);
    pthread_create(&id, NULL, P4, NULL);
    pthread_create(&id, NULL, P5, NULL);

    sleep(1);
    delete sem1;
    delete sem2;
    delete sem3;
    delete sem4;
    delete sem5;
    return 0;
}

编译运行的结果如下:

Philosopher 2 eat.
Philosopher 1 eat.
Philosopher 3 eat.
Philosopher 4 eat.
Philosopher 5 eat.

注意:创建信号量时的 路径参数 请改成你的系统中存在的路径!!!





附:semaphore类的封装

上面的代码中都使用了这个semaphore类,实现如下:

  • semaphore.h
#pragma once
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<sys/sem.h>
using namespace std;

// 联合体,用于semctl初始化
union semun {
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};


class semaphore {
private:
    int sem_id;
    int init_sem(int);
public:
    semaphore(const char*, int); /*构造函数*/
    ~semaphore();                /*析构函数*/
    void P();                    /*P操作*/
    void V();                    /*V操作*/
};
  • semaphore.cpp
#include"semaphore.h"

semaphore::semaphore(const char* path, int value)
{
    key_t key;
    /*获取key值*/
    if((key = ftok(path, 'z')) < 0) {
        perror("ftok error");
        exit(1);
    }

    /*创建信号量集,其中只有一个信号量*/
    if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1) {
        perror("semget error");
        exit(1);
    }

    init_sem(value);
}


semaphore::~semaphore()
{
    union semun tmp;
    if(semctl(sem_id, 0, IPC_RMID, tmp) == -1) {
        perror("Delete Semaphore Error");
        exit(1);
    }
}


void semaphore::P()
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1) {
        perror("P operation Error");
    }
}


void semaphore::V()
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1) {
        perror("V operation Error");
    }
}


// 初始化信号量
int semaphore::init_sem(int value)
{
    union semun tmp;
    tmp.val = value;
    if(semctl(sem_id, 0, SETVAL, tmp) == -1) {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

在这里,要创建不同的信号量,必须传递不同的路径参数(这样获取的 key 值才会不一样)。

注意,本文的关注点并不在于 linux 下如何创建信号量以及如何封装起来才更方便,而是通过几个经典的同步实例,了解在多线程环境下如何解决这类线程同步问题。







个人站点:http://songlee24.github.com

  • 1
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
同步概念 所谓同步,即同时起步,协调一致。不同的对象,对“同步”的理解方式略有不同。如,设备同步,是指在两个设备之间规定一个共同的时间参考;数据库同步,是指让两个或多个数据库内容保持一致,或者按需要部分保持一致;文件同步,是指让两个或多个文件夹里的文件保持一致。等等 而,编程中、通信中所说的同步与生活中大家印象中的同步概念略有差异。“同”字应是指协同、协助、互相配合。主旨在协同步调,按预定的先后次序运行。 线程同步 同步即协同步调,按预定的先后次序运行。 线程同步,指一个线程发出某一功能调用时,在没有得到结果之前,该调用不返回。同时其它线程为保证数据一致性,不能调用该功能。 举例1: 银行存款 5000。柜台,折:取3000;提款机,卡:取 3000。剩余:2000 举例2: 内存中100字节,线程T1欲填入全1, 线程T2欲填入全0。但如果T1执行了50个字节失去cpu,T2执行,会将T1写过的内容覆盖。当T1再次获得cpu继续 从失去cpu的位置向后写入1,当执行结束,内存中的100字节,既不是全1,也不是全0。 产生的现象叫做“与时间有关的错误”(time related)。为了避免这种数据混乱,线程需要同步。 “同步”的目的,是为了避免数据混乱,解决与时间有关的错误。实际上,不仅线程间需要同步,进程间、信号间等等都需要同步机制。 因此,所有“多个控制流,共同操作一个共享资源”的情况,都需要同步。 数据混乱原因: 1. 资源共享(独享资源则不会) 2. 调度随机(意味着数据访问会出现竞争) 3. 线程间缺乏必要的同步机制。 以上3点中,前两点不能改变,欲提高效率,传递数据,资源必须共享。只要共享资源,就一定会出现竞争。只要存在竞争关系,数据就很容易出现混乱。 所以只能从第三点着手解决。使多个线程访问共享资源的时候,出现互斥。 互斥量mutex Linux中提供一把互斥锁mutex(也称之为互斥量)。 每个线程在对资源操作前都尝试先加锁,成功加锁才能操作,操作结束解锁。 资源还是共享的,线程间也还是竞争的, 但通过“锁”就将资源的访问变成互斥操作,而后与时间有关的错误也不会再产生了。 但,应注意:同一时刻,只能有一个线程持有该锁。 当A线程对某个全局变量加锁访问,B在访问前尝试加锁,拿不到锁,B阻塞。C线程不去加锁,而直接访问该全局变量,依然能够访问,但会出现数据混乱。 所以,互斥锁实质上是操作系统提供的一把“建议锁”(又称“协同锁”),建议程序中有多线程访问共享资源的时候使用该机制。但,并没有强制限定。 因此,即使有了mutex,如果有线程不按规则来访问数据,依然会造成数据混乱。 主要应用函数: pthread_mutex_init函数 pthread_mutex_destroy函数 pthread_mutex_lock函数 pthread_mutex_trylock函数 pthread_mutex_unlock函数 以上5个函数的返回值都是:成功返回0, 失败返回错误号。 pthread_mutex_t 类型,其本质是一个结构体。为简化理解,应用时可忽略其实现细节,简单当成整数看待。 pthread_mutex_t mutex; 变量mutex只有两种取值1、0。 pthread_mutex_init函数 初始化一个互斥锁(互斥量) ---> 初值可看作1 int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); 参1:传出参数,调用时应传 &mutex restrict关键字:只用于限制指针,告诉编译器,所有修改该指针指向内存中内容的操作,只能通过本指针完成。不能通过除本指针以外的其他变量或指针修改 参2:互斥量属性。是一个传入参数,通常传NULL,选用默认属性(线程间共享)。 参APUE.12.4同步属性 1. 静态初始化:如果互斥锁 mutex 是静态分配的(定义在全局,或加了static关键字修饰),可以直接使用宏进行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER; 2. 动态初始化:局部变量应采用动态初始化。e.g. pthread_mutex_init(&mutex, NULL) pthread_mutex_destroy函数 销毁一个互斥锁 int pthread_mutex_destroy(pthread_mutex_t *mutex); pthread_mutex_lock函数 加锁。可理解为将mutex--(或-1) int pthread_mutex_lock(pthread_mutex_t *mutex); pthread_mutex_unlock函数 解锁。可理解为将mutex ++(或+1) int pthread_mutex_unlock(pthread_mutex_t *mutex); pthread_mutex_trylock函数 尝试加锁 int pthread_mutex_trylock(pthread_mutex_t *mutex); 加锁与解锁 lock与unlock: lock尝试加锁,如果加锁不成功,线程阻塞,阻塞到持有该互斥量的其他线程解锁为止。 unlock主动解锁函数,同时将阻塞在该锁上的所有线程全部唤醒,至于哪个线程先被唤醒,取决于优先级、调度。默认:先阻塞、先唤醒。 例如:T1 T2 T3 T4 使用一把mutex锁。T1加锁成功,其他线程均阻塞,直至T1解锁。T1解锁后,T2 T3 T4均被唤醒,并自动再次尝试加锁。 可假想mutex锁 init成功初值为1。 lock 功能是将mutex--。 unlock将mutex++ lock与trylock: lock加锁失败会阻塞,等待锁释放。 trylock加锁失败直接返回错误号(如:EBUSY),不阻塞。 加锁步骤测试: 看如下程序:该程序是非常典型的,由于共享、竞争而没有加任何同步机制,导致产生于时间有关的错误,造成数据混乱: #include #include #include void *tfn(void *arg) { srand(time(NULL)); while (1) { printf("hello "); sleep(rand() % 3); /*模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误*/ printf("world\n"); sleep(rand() % 3); } return NULL; } int main(void) { pthread_t tid; srand(time(NULL)); pthread_create(&tid, NULL, tfn, NULL); while (1) { printf("HELLO "); sleep(rand() % 3); printf("WORLD\n"); sleep(rand() % 3); } pthread_join(tid, NULL); return 0; } 【mutex.c】 【练习】:修改该程序,使用mutex互斥锁进行同步。 1. 定义全局互斥量,初始化init(&m, NULL)互斥量,添加对应的destry 2. 两个线程while中,两次printf前后,分别加lock和unlock 3. 将unlock挪至第二个sleep后,发现交替现象很难出现。 线程在操作完共享资源后本应该立即解锁,但修改后,线程抱着锁睡眠。睡醒解锁后又立即加锁,这两个库函数本身不会阻塞。 所以在这两行代码之间失去cpu的概率很小。因此,另外一个线程很难得到加锁的机会。 4. main 中加flag = 5 将flg在while中-- 这时,主线程输出5次后试图销毁锁,但子线程未将锁释放,无法完成。 5. main 中加pthread_cancel()将子线程取消。 【pthrd_mutex.c】 结论: 在访问共享资源前加锁,访问结束后立即解锁。锁的“粒度”应越小越好。 死锁 1. 线程试图对同一个互斥量A加锁两次。 2. 线程1拥有A锁,请求获得B锁;线程2拥有B锁,请求获得A锁 【作业】:编写程序,实现上述两种死锁现象。 读写锁 与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享。 读写锁状态: 一把读写锁具备三种状态: 1. 读模式下加锁状态 (读锁) 2. 写模式下加锁状态 (写锁) 3. 不加锁状态 读写锁特性: 1. 读写锁是“写模式加锁”时, 解锁前,所有对该锁加锁的线程都会被阻塞。 2. 读写锁是“读模式加锁”时, 如果线程以读模式对其加锁会成功;如果线程以写模式加锁会阻塞。 3. 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高 读写锁也叫共享-独占锁。当读写锁以读模式锁住时,它是以共享模式锁住的;当它以写模式锁住时,它是以独占模式锁住的。写独占、读共享。 读写锁非常适合于对数据结构读的次数远大于写的情况。 主要应用函数: pthread_rwlock_init函数 pthread_rwlock_destroy函数 pthread_rwlock_rdlock函数 pthread_rwlock_wrlock函数 pthread_rwlock_tryrdlock函数 pthread_rwlock_trywrlock函数 pthread_rwlock_unlock函数 以上7 个函数的返回值都是:成功返回0, 失败直接返回错误号。 pthread_rwlock_t类型 用于定义一个读写锁变量。 pthread_rwlock_t rwlock; pthread_rwlock_init函数 初始化一把读写锁 int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); 参2:attr表读写锁属性,通常使用默认属性,传NULL即可。 pthread_rwlock_destroy函数 销毁一把读写锁 int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); pthread_rwlock_rdlock函数 以读方式请求读写锁。(常简称为:请求读锁) int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); pthread_rwlock_wrlock函数 以写方式请求读写锁。(常简称为:请求写锁) int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); pthread_rwlock_unlock函数 解锁 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); pthread_rwlock_tryrdlock函数 非阻塞以读方式请求读写锁(非阻塞请求读锁) int pthread_

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值