欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

多线程互斥-读写者问题

程序员文章站 2022-07-05 09:04:11
...

互斥量(mutex)

互斥锁创建

有两种方法创建互斥锁,静态方式和动态方式。POSIX定义了一个宏PTHREAD_MUTEX_INITIALIZER 来静态初始化互斥锁,方法如下:

  1. pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;在LinuxThreads实现中, pthread_mutex_t是一个结构,而PTHREAD_MUTEX_INITIALIZER则是一个结构常量。

  2. 动态方式是采用pthread_mutex_init()函数来初始化互斥锁,API定义如下:

     int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t
             *mutexattr)

    其中mutexattr用于指定互斥锁属性(见下),如果为NULL则使用缺省属性。 pthread_mutex_destroy ()用于注销一个互斥锁,API定义如下:

     int pthread_mutex_destroy(pthread_mutex_t *mutex)

锁操作

锁操作主要包括加锁pthread_mutex_lock()、解锁pthread_mutex_unlock()和测试加锁 pthread_mutex_trylock()三个,不论哪种类型的锁,都不可能被两个不同的线程同时得到, 而必须等待解锁。对于普通锁和适应锁类型,解锁者可以是同进程内任何线程; 而检错锁则必须由加锁者解锁才有效,否则返回EPERM;对于嵌套锁,文档和实现要求必须由 加锁者解锁,但实验结果表明并没有这种限制,这个不同目前还没有得到解释。在同一进程中 的线程,如果加锁后没有解锁,则任何其他线程都无法再获得锁。

int pthread_mutex_lock(pthread_mutex_t *mutex)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
int pthread_mutex_trylock(pthread_mutex_t *mutex)

pthread_mutex_trylock() 语义与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回 EBUSY而不是挂起等待。

 

读写者问题:

读者————写者问题是一个用信号量实现的经典进程同步问题。在系统中,一个数据集(如文件或 记录) 被几个并发进程共享,这些线程分两类,一部分只要求进行读操作,称之为“读者”; 另一类要求写或修改操作,我们称之为“写者“。一般而言,对一个数据集,为了保证数据的完整 性、正确性,允许多个读者进程同时访问,但是不允许一个写者进程同其它任何一个进程(读者 或者写者)同时访问,而这类问题就称之为"读者-写者"问题。

/*********    说明:
*********    1.要让读者与写者之间、以及写者与写者之问要互斥地访同数据集;
*********    2.在无写进程到来时各读者可同时访问数据集;
*********    3.在读者和写者都等待时访问时写者优先.
*********/

#include <pthread.h>
#include <signal.h>
#include <stdio.h>
//#include "apue.h"

#define R 5 // reader NO.
#define W 5 // reader and writer NO.

pthread_mutex_t rd = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t wr = PTHREAD_MUTEX_INITIALIZER;

int readCount = 0; //临界资源

void* reader(void *arg)
{
    int n = W;
    int id = *(int *)arg;
    while (n --)
    {
         sleep(rand()%3);
         pthread_mutex_lock(&rd);
         readCount++; //对于临街资源必须做到互斥访问,同一时间只有一个线程在操作.
         if( readCount ==1 ) //当只要有读线程时就会锁住,并不用>1时都锁住,只要锁一次,其他>1的情况均没有释放,在等于0的时候释放就行
         {
             pthread_mutex_lock(&wr);
         }
         pthread_mutex_unlock(&rd);
         printf("reader %d is reading, current read count[%d]\n", id, readCount); //这段代码表示多个读者之间是可以并行执行的.
         sleep(rand()%3);
         pthread_mutex_lock(&rd);
         readCount--;

         if(readCount == 0)
         {
              pthread_mutex_unlock(&wr);
         }
         pthread_mutex_unlock(&rd);
         printf("reader %d is leaving, current read count[%d]\n", id, readCount);
    }
    printf("-----reader %d has done ----, current read count[%d]\n", id, readCount);
}

void *writer(void *arg)
{
     int n = W;
     while(n--)
     {
         sleep(rand()%3);
         pthread_mutex_lock(&wr); //写线程之间也是互斥的.
         printf("\twriter is writing\n");
         sleep(rand()%3);
         pthread_mutex_unlock(&wr);
         printf("\twriter is leaving\n");
     }
     printf("-----writer has done -----\n");
}

int main(int argc, const char *argv[])
{
     int err;
     pthread_t tid[R], writerTid;
     int i;

     err = pthread_create(&writerTid, NULL, writer, (void *)NULL);
     if (err != 0)
     {
          printf("can't create process for writer");
     }

     for(i = 0; i < R; i++)
     {
         err = pthread_create(&tid[i], NULL, reader, &i);
         if (err != 0)
         {
             printf("can't create process for reader");
         }
     }

     //pause();
     pthread_join(tid[0],&err);
     printf("thread 1 exit code %d\n", (int)err);

     pthread_join(tid[1],&err);
     printf("thread 2 exit code %d\n",(int)err);

     pthread_join(tid[2],&err);
     printf("thread 3 exit code %d\n", (int)err);

     pthread_join(tid[3],&err);
     printf("thread 4 exit code %d\n", (int)err);

     pthread_join(tid[4],&err);
     printf("thread 5 exit code %d\n", (int)err);
     //while(1);
     pthread_join(writerTid,&err);
     printf("write thread  exit code %d\n", (int)err);
     return 0;
}

运行结果是:

reader 2 is reading, current read count[1]
reader 1 is reading, current read count[2]
reader 1 is leaving, current read count[2]
reader 1 is reading, current read count[3]
reader 3 is reading, current read count[3]
reader 5 is reading, current read count[4]
reader 2 is leaving, current read count[3]
reader 4 is reading, current read count[4]
reader 1 is leaving, current read count[3]
reader 5 is leaving, current read count[2]
reader 3 is leaving, current read count[1]
reader 3 is reading, current read count[2]
reader 3 is leaving, current read count[1]
reader 4 is leaving, current read count[0]
    writer is writing
    writer is leaving
reader 5 is reading, current read count[1]
reader 5 is leaving, current read count[2]
reader 4 is reading, current read count[3]
reader 3 is reading, current read count[5]
reader 1 is reading, current read count[5]
reader 5 is reading, current read count[4]
reader 2 is reading, current read count[2]
reader 5 is leaving, current read count[4]
reader 2 is leaving, current read count[3]
reader 4 is leaving, current read count[2]
reader 4 is reading, current read count[3]
reader 4 is leaving, current read count[2]
reader 4 is reading, current read count[3]
reader 3 is leaving, current read count[2]
reader 3 is reading, current read count[3]
reader 1 is leaving, current read count[2]
reader 5 is reading, current read count[3]
reader 2 is reading, current read count[4]
reader 3 is leaving, current read count[3]
reader 5 is leaving, current read count[3]
reader 1 is reading, current read count[4]
reader 1 is leaving, current read count[4]
reader 3 is reading, current read count[4]
reader 5 is reading, current read count[5]
reader 4 is leaving, current read count[3]
reader 2 is leaving, current read count[2]
reader 5 is leaving, current read count[1]
-----reader 5 has done ----, current read count[1]
reader 3 is leaving, current read count[0]
-----reader 3 has done ----, current read count[1]
reader 1 is reading, current read count[1]
reader 4 is reading, current read count[2]
reader 4 is leaving, current read count[1]
-----reader 4 has done ----, current read count[1]
reader 2 is reading, current read count[2]
reader 1 is leaving, current read count[1]
-----reader 1 has done ----, current read count[1]
thread 1 exit code 51
reader 2 is leaving, current read count[0]
    writer is writing
    writer is leaving
reader 2 is reading, current read count[1]
reader 2 is leaving, current read count[0]
-----reader 2 has done ----, current read count[0]
    writer is writing
    writer is leaving
thread 2 exit code 51
thread 3 exit code 51
thread 4 exit code 51
thread 5 exit code 51
    writer is writing
    writer is leaving
    writer is writing
    writer is leaving
-----writer has done -----
write thread  exit code 27

从log可以看出这是个读者优先的算法这是一种最简单的解决办法: 当没有写进程正在访问共享数据集时,读进程可以进入访问,否则必须等待。当读者访问完毕,写者才有机会获得临界区的访问权,所以说是读者优先.而读者优先的算法存在"饿死 写者"线程的问题:只要有读者不断到来,写者就要持久地等待,直到所有的读者都读完且没有新的读者到来时写者才能写数据集。而在很多情况下我们需要避免"饿死写者",故而采用写者优先算法:

在写者优先算法中,我们要实现的目标是:

1.要让读者与写者之间、以及写者与写者之问要互斥地访同数据集;

2.在无写进程到来时各读者可同时访问数据集;

3.在读者和写者都等待时访问时写者优先.

在实现写者优先时,增加一个互斥量,用于写者优先。当有写者来时,就不在允许读者去读取数据, 等待正在读数据的读者完成以后开始写数据,以此实现写者优先。

写者优先的代码如下:

/*********    说明:
*********    1.要让读者与写者之间、以及写者与写者之问要互斥地访同数据集;
*********    2.在无写进程到来时各读者可同时访问数据集;
*********    3.在读者和写者都等待时访问时写者优先.
*********/

#include <pthread.h>
#include <signal.h>
#include <stdio.h>
//#include "apue.h"

#define R 5 // reader NO.
#define W 5 // reader and writer NO.

pthread_mutex_t critical = PTHREAD_MUTEX_INITIALIZER; //保护临界资源互斥
pthread_mutex_t rd = PTHREAD_MUTEX_INITIALIZER; //保护读互斥
pthread_mutex_t wr = PTHREAD_MUTEX_INITIALIZER; //保护写互斥
pthread_mutex_t priority = PTHREAD_MUTEX_INITIALIZER;//读写互斥

int readCount = 0; //临界资源
int writeCount = 0; //临界资源

void* reader(void *arg)
{
    int n = W;
    int id = *(int *)arg;
    while (1)
    {
         sleep(rand()%3);
         pthread_mutex_lock(&priority);
         pthread_mutex_lock(&rd);
         readCount++; //对于临街资源必须做到互斥访问,同一时间只有一个线程在操作.
         if(readCount == 1)
             pthread_mutex_lock(&critical);
         pthread_mutex_unlock(&rd);
         pthread_mutex_unlock(&priority);
         printf("reader %d is reading, current read count[%d]\n", id, readCount); //这段代码表示多个读者之间是可以并行执行的.
         sleep(rand()%3);
         pthread_mutex_lock(&rd);//可以看到释放资源这里不需要获取priority锁.
         readCount--;
         if(readCount == 0)
             pthread_mutex_unlock(&critical);
         pthread_mutex_unlock(&rd);
         printf("reader %d is leaving, current read count[%d]\n", id, readCount);
    }
    printf("-----reader %d has done ----, current read count[%d]\n", id, readCount);
}

void *writer(void *arg)
{
     int n = W;
     while(1)
     {
         sleep(rand()%3);
         pthread_mutex_lock(&wr);
         if(writeCount == 0)
             pthread_mutex_lock(&priority);
         writeCount++;
         pthread_mutex_unlock(&wr);

         pthread_mutex_lock(&critical); //写线程之间也是互斥的.
         printf("\twriter is writing\n");
         pthread_mutex_unlock(&critical);

         pthread_mutex_lock(&wr);
         writeCount--;
         if(writeCount == 0)
             pthread_mutex_unlock(&priority);
         pthread_mutex_unlock(&wr);
         printf("\twriter is leaving \n");
     }
     printf("-----writer has done -----\n");
}

int main(int argc, const char *argv[])
{
     int err;
     pthread_t tid[R], writerTid;
     int i= 0;
     //for(i; i < W; ++i){
         err = pthread_create(&tid[i], NULL, reader, &i);
         if (err != 0)
         {
             printf("can't create process for reader");
         }
     //}

     err = pthread_create(&writerTid, NULL, writer, (void *)NULL);
     if (err != 0)
     {
          printf("can't create process for writer");
     }

     while(1);
     return 0;
}

分析:写者优先与读者优先相类似。不同之处在于一旦一个写者到来,它应该尽快对文件进行写操作,如果有一个写者在等待,则新到来的读者不允许进行读操作。为此应当填加一个整形变量writeCount,用于记录正在等待的写者的数目,当Writecount=0时,才可以释放等待的读者线程队列。

每个读进程最开始都要申请一下priority信号量,之后在真正做读操作前即让出(使得写进程可以随时申请到 priority)。而只有第一个写进程需要申请 priority,之后就一直占着不放了,直到所有写进程都完成后才让出。等于只要有写进程提出申请就禁止读进程排队,变相提高了写进程的优先级

部分运行结果如下:

reader 0 is reading, current read count[1]
reader 0 is leaving, current read count[0]
    writer is writing
    writer is leaving 
reader 0 is reading, current read count[1]
reader 0 is leaving, current read count[0]
    writer is writing
    writer is leaving 
    writer is writing
    writer is leaving 
    writer is writing
    writer is leaving 
reader 0 is reading, current read count[1]
reader 0 is leaving, current read count[0]
    writer is writing
    writer is leaving 
reader 0 is reading, current read count[1]
    writer is writing
    writer is leaving 
reader 0 is leaving, current read count[0]
    writer is writing
    writer is leaving 
reader 0 is reading, current read count[1]
reader 0 is leaving, current read count[0]
    writer is writing
    writer is leaving 
    writer is writing
    writer is leaving 
    writer is writing
    writer is leaving 
reader 0 is reading, current read count[1]
reader 0 is leaving, current read count[0]
    writer is writing
    writer is leaving 
    writer is writing
    writer is leaving 
    writer is writing
    writer is leaving 
reader 0 is reading, current read count[1]
reader 0 is leaving, current read count[0]
    writer is writing
    writer is leaving 
    writer is writing
    writer is leaving 
    writer is writing
    writer is leaving 
    writer is writing
    writer is leaving 
    writer is writing
    writer is leaving 
reader 0 is reading, current read count[1]
reader 0 is leaving, current read count[0]
    writer is writing
    writer is leaving 
reader 0 is reading, current read count[1]

可以看出来,write的优先级更高.

继续衍生,读者-写者公平竞争的代码:

/*********    说明:
*********    1.要让读者与写者之间、以及写者与写者之问要互斥地访同数据集;
*********    2.在无写进程到来时各读者可同时访问数据集;
*********    3.在读者和写者都等待时访问时写者优先.
*********/

#include <pthread.h>
#include <signal.h>
#include <stdio.h>
//#include "apue.h"

#define R 5 // reader NO.
#define W 5 // reader and writer NO.

pthread_mutex_t critical = PTHREAD_MUTEX_INITIALIZER; //保护临界资源互斥
pthread_mutex_t rd = PTHREAD_MUTEX_INITIALIZER; //保护读互斥
pthread_mutex_t wr = PTHREAD_MUTEX_INITIALIZER; //保护写互斥
pthread_mutex_t priority = PTHREAD_MUTEX_INITIALIZER;//读写互斥

int readCount = 0; //临界资源
int writeCount = 0; //临界资源

void* reader(void *arg)
{
    int n = W;
    int id = *(int *)arg;
    while (1)
    {
         sleep(rand()%3);
         pthread_mutex_lock(&priority);
         pthread_mutex_lock(&rd);
         readCount++; //对于临街资源必须做到互斥访问,同一时间只有一个线程在操作.
         if(readCount == 1)
             pthread_mutex_lock(&critical);
         pthread_mutex_unlock(&rd);
         pthread_mutex_unlock(&priority);
         printf("reader %d is reading, current read count[%d]\n", id, readCount); //这段代码表示多个读者之间是可以并行执行的.
         sleep(rand()%3);
         pthread_mutex_lock(&rd);//可以看到释放资源这里不需要获取priority锁.
         readCount--;
         if(readCount == 0)
             pthread_mutex_unlock(&critical);
         pthread_mutex_unlock(&rd);
         printf("reader %d is leaving, current read count[%d]\n", id, readCount);
    }
    printf("-----reader %d has done ----, current read count[%d]\n", id, readCount);
}

void *writer(void *arg)
{
     int n = W;
     while(1)
     {
         sleep(rand()%3);
         pthread_mutex_lock(&priority);
         pthread_mutex_lock(&critical); //写线程之间也是互斥的.
         printf("\twriter is writing\n");
         pthread_mutex_unlock(&priority); /*注意这里两个信号量的位置,这样写可以尽快释放priority供读者调用,提高效率*/
         pthread_mutex_unlock(&critical);
     }
     printf("-----writer has done -----\n");
}

int main(int argc, const char *argv[])
{
     int err;
     pthread_t tid[R], writerTid;
     int i= 0;
     //for(i; i < W; ++i){
         err = pthread_create(&tid[i], NULL, reader, &i);
         if (err != 0)
         {
             printf("can't create process for reader");
         }
     //}

     err = pthread_create(&writerTid, NULL, writer, (void *)NULL);
     if (err != 0)
     {
          printf("can't create process for writer");
     }

     while(1);
     return 0;
}

读线程不变,写线程变成需要获取priority信号量.