- 原始问题
一个数据文件或记录,可被多个进程共享,我们把只要求读该文件的进程称为“读者进程”,其他进程则称为“写者进程”。允许多个进程同时读一个共享对象,因为读操作不会使数据文件混乱。但不允许一个写者进程和其他读者进程或写者进程同时访问共享对象。因为这种访问将会引起混乱。
- 模型描述
对共享资源的读写操作,任一时刻“写者”最多只允许一个,“读-写”互斥,“写-写”互斥。
当读进程reader要访问数据记录有以下几种情况:
1、无进程在访问,顺利进入临界区,访问临界资源。
2、已有读进程在访问,此时仍可访问临界资源,但要记录有几个读进程在访问临界资源。
3、已有写进程在访问,reader阻塞。
当写进程writer要访问数据记录有以下几种情况:
1、无进程在访问,顺利进入临界区,访问临界资源
2、已有读进程在访问,writer阻塞
3、已有写进程在访问,writer阻塞
解决读者-写者问题的方法通常涉及使用信号量、互斥锁和条件变量等同步机制。有几种策略可以采用,分别是公平竞争,读优先,写优先。
- 技术分析
设置三个信号量:信号量0 (semid, 0):用于控制写者的访问;信号量1 (semid, 1):用于控制读者的访问;信号量2 (semid, 2):用于封锁。
公平竞争: 在Writer1 中,写者首先等待信号量2,以确保没有其他写者或读者同时访问共享资源。然后,它请求信号量0,以确保没有其他写者也在写。一旦获得信号量0,它执行写操作,然后释放信号量0和信号量2,允许其他写者和读者继续操作。
在 Reader1 中,读者也首先等待信号量2以确保没有其他写者或读者同时访问共享资源。然后,它请求信号量1,以确保没有其他读者同时读。如果读者是第一个读者,它会检查 shm->rCount 是否为0,如果是,它请求信号量0,以阻止写者进入。然后,它增加 shm->rCount 表示有读者在读。接下来,它释放信号量1和信号量2,允许其他读者和写者继续操作。读者执行读操作,然后再次请求信号量1,减少 shm->rCount,并检查是否它是最后一个读者,如果是,它释放信号量0,允许写者进入。
读者优先: 在Writer2中,写者首先请求信号量0,以确保没有其他写者或读者在同时访问共享资源。然后,它执行写操作,最后,它释放信号量0,允许其他写者或读者进入。写者没有等待信号量1,不阻止读者进入,因此读者有更多的机会访问共享资源。
在Reader2中,读者首先请求信号量1,以确保没有其他读者在同时读。如果读者是第一个读者(即 shm->rCount == 0),它请求信号量0,以阻止写者进入。它增加 shm->rCount 表示有读者在读,并释放信号量1,允许其他读者进入。它执行读操作后,它请求信号量2,减少 shm->rCount,并检查是否它是最后一个读者,如果是,它释放信号量0,允许写者进入。这也确保了读者优先策略,即只有在没有读者时,写者才能进入。最后,它释放信号量2,允许其他读者或写者进入。
写者优先: 在Writer3中,写者首先请求信号量2,以确保没有其他写者或读者在同时访问共享资源。然后,它请求信号量1,以确保没有其他读者在同时读。这一步是为了确保写者在没有读者时才能进入。如果写者是第一个写者(即 shm->wCount == 0),它请求信号量0,以阻止其他写者进入。然后,它增加 shm->wCount 表示有写者在写,然后释放信号量1和信号量2,允许其他写者或读者进入,执行写操作完后,它再次请求信号量1,减少 shm->wCount,并检查是否它是最后一个写者,如果是,它释放信号量0,允许其他写者进入。这也确保了写者优先策略,即只有在没有其他写者时,写者才能连续进入。最后,它释放信号量1,允许其他写者或读者进入。
在 Reader3中,读者首先请求信号量0,以确保没有其他写者在同时写。这是写者优先策略的要点,读者必须等待没有写者时才能进入。它执行读操作,
最后,它释放信号量0。
- 结果分析
通过调用Writer1(semid, shm),Reader1(semid, shm),得到以下结果:
从上图我们可以看出程序大部分执行读写进程的时间大致相同,无论读进程还是写进程到来,都需要排队,遵循先到先得的原则。进一步验证了读写公平竞争策略的正确性。
通过调用Writer2(semid, shm),Reader2(semid, shm),得到以下结果:
从上图我们可以看出程序大部分时间都在执行读进程,当有程序进行读时,不会有写进程执行写操作。进一步验证了读优先策略的正确性。
通过调用Writer3(semid, shm),Reader3(semid, shm),得到以下结果:
从上图我们可以看出程序大部分时间都在执行写进程,当有写进程等待时,不会有读进程执行读操作。进一步验证了写优先策略的正确性。
- 总结与思考
实现解决读者与写者问题的三个策略并不简单,编写程序的过程中也出现了不少问题,通过分析和理解,得到了解决。程序非常容易出现死锁,需要特别注意理清逻辑关系,避免出现相互等待对方释放资源的情况,且要小心出现优先级反转的情况。程序需要设定一个循环退出的条件,不然程序就会一直执行下去。这次作业让我更加深刻地理解了进程信号量和共享内存的概念和应用,提升了我对并发访问问题的理解和解决能力。
#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#define SEMKEY 123
#define SHMKEY 456
int count = 0;//to end while
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
};
struct Buffer
{
int length, wCount, rCount;
char buffer[100];
};
void Write(struct Buffer *shm)
{
char ch = 'A' + rand() % 26;
printf("writer %d: wrote %c\t", getpid(), ch);
shm -> buffer [shm -> length ++] = ch;
shm -> buffer [shm -> length] = '\0';
printf("%s\n", shm -> buffer);
if (shm->length >= 20)
{
printf("writer %d: deleted\n", getpid());
shm->length = 0;
}
}
void Read(struct Buffer *shm)
{
printf("Reader %d: read\t", getpid());
printf("%s\n", shm -> buffer);
}
void P(int semid, int semnum)
{
struct sembuf op;
op.sem_num = semnum;
op.sem_op = -1;
op.sem_flg = SEM_UNDO;
semop(semid, &op, 1);
}
void V(int semid, int semnum)
{
struct sembuf op;
op.sem_num = semnum;
op.sem_op = 1;
op.sem_flg = 0;
semop(semid, &op, 1);
}
//fair
void Writer1(int semid, struct Buffer *shm)
{
while(count < 20)
{
P(semid, 2);//s semaphore
P(semid, 0);//writer semaphore
Write(shm);
V(semid, 0);
V(semid, 2);
sleep(random() % 2);
count++;
}
}
void Reader1(int semid, struct Buffer *shm)
{
while(count < 20)
{
P(semid, 2);
P(semid, 1);//read semaphore
if(shm -> rCount == 0)
P(semid, 0);
shm -> rCount++;
V(semid, 1);
V(semid, 2);
Read(shm);
P(semid, 1);
shm -> rCount--;
if(shm -> rCount == 0)
V(semid, 0);
V(semid, 1);
sleep(random() % 2);
count++;
}
}
//read-preferred
void Writer2(int semid, struct Buffer *shm)
{
while(count < 20)
{
P(semid, 0);
Write(shm);
V(semid, 0);
sleep(random() % 2);
count++;
}
}
void Reader2(int semid, struct Buffer *shm)
{
while(count < 20)
{
P(semid, 1);
if(shm -> rCount == 0)
P(semid, 0);
shm -> rCount++;
V(semid, 1);
Read(shm);
P(semid, 2);
shm -> rCount--;
if(shm -> rCount == 0) V(semid, 0);
V(semid, 2);
sleep(random() % 2);
count++;
}
}
//write-preferred
void Writer3(int semid, struct Buffer *shm)
{
while(count < 20)
{
P(semid, 2);
P(semid, 1);
if(shm -> wCount == 0)
P(semid, 0);
shm -> wCount++;
V(semid, 1);
V(semid, 2);
Write(shm);
P(semid, 1);
shm -> wCount--;
if(shm -> wCount == 0)
V(semid, 0);
V(semid, 1);
sleep(random() % 2);
count++;
}
}
void Reader3(int semid, struct Buffer *shm)
{
while(count < 20)
{
P(semid, 0);
Read(shm);
V(semid, 0);
sleep(random() % 2);
count++;
}
}
int main()
{
int v[4] = {1, 1, 1, 1}, semid = semget(SEMKEY, 4, IPC_CREAT | 0666);
union semun semUn;
for(int i = 0; i < 4; i ++)
{
semUn.val = v[i];
semctl(semid, i, SETVAL, semUn);
}
int shmid = shmget(SHMKEY, sizeof(struct Buffer), IPC_CREAT | 0666);
struct Buffer *shm = (struct Buffer *) shmat(shmid, 0, 0);
shm -> length = 0;
shm -> wCount = 0;
shm -> rCount = 0;
shm -> buffer[0] = '\0';
for(int i = 0; i < 8 ; i ++)//4 writer 4 reader
{
pid_t pid = fork();
if(pid < 0)
{
printf("fork failed!\n");
exit(1);
}
else if(pid == 0)
{
sleep(1);
if(i % 2 == 0)
{
printf("writer process %d created\n", getpid());
//Writer1(semid, shm);
//Writer2(semid, shm);
Writer3(semid, shm);
exit(0);
}
else
{
printf("reader process %d created\n", getpid());
// Reader1(semid, shm);
// Reader2(semid, shm);
Reader3(semid, shm);
exit(0);
}
}
}
semctl(semid, 0, IPC_RMID, 0);
shmctl(shmid, IPC_RMID, 0);
return 0;
}