p { margin-bottom: 0.21cm; }
/*
* ReaderWriter.c
* 题目 :2 :
* Unix 下读者写者同步问题的模拟程序 要求 4 个读进程 2 个写进程。
* 采用信号量方式实现。可以采用 Pthread 多线程编程。
*
* Created on: 2011-1-17
* Author: banxi1988
*
* 信号量通信机制主要用来实现进程间同步 , 信号 量值用来标识系统可用 资源的个数
* 例如可以使用信号号来标识一个缓冲区空间的大小 ,( 假定缓冲区空间大小为 256 个字节 .
* 在没有使用之前 , 该缓冲区没有任何内容 , 可用资源为 256, 即可以初始化信号 量为 256,
* 每向缓冲区写入一个字符 , 信号量的值自动减 1, 当信号量的值为 0 时 , 即表示缓冲区满 .
* 资源暂不可用 . 每从缓冲区读出一个字符 , 信号 时自动加 1, 如果信号量的值为 256, 则表示
* 缓冲区中没有内容 , 不可读 .
* 信号量初始化函数参考 :
* // Initialize semaphore object SEM to VALUE. If PSHARED then share it
// with other processes.
extern int sem_init (sem_t *__sem, int __pshared, unsigned int __value)
简单翻译如下 , 将信号量对象 SEM 初始化为 VALUE 值 , 当信号量的值达到 PSHARED 时 , 将其分享给其它进程 .
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <semaphore.h>
#define WRITER 2 // 写者数量 . 2
#define READER 4 // 读者数量 4
#define BUFFER_SIZE 8 // 缓冲区 ( 大小 为 8 )
int writeOffset = 0; // 写者写内容的位置
int readOffset = 0; // 读者取内容的位置
int buffer[BUFFER_SIZE] = { '#' }; // '#' 字符代表空间没有内容
sem_t empty_sem; // 同步信号量, 当满了时阻止写者写 .
sem_t full_sem; // 同步信号量, 当没有内容时阻止读者读
pthread_mutex_t mutexlock; // 互斥锁, 一次只有一个线程访问缓冲
char letters[52] = { "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" };
/*
* 打印缓冲情况
*/
void printBuffer () {
int i;
printf ( "/t 当前缓冲区内容 :/t" );
for (i = 0; i < BUFFER_SIZE; i++)
printf ( "%c " , buffer[i]);
printf ( "/n" );
}
void writer ( int *id) {
srand (( unsigned ) time(NULL));
int index = 0;
while (1) {
sleep (1); // 先等待
//sem_wait( &empty_sem );// 等待缓冲区可写信号 .
pthread_mutex_lock (&mutexlock);
// 下面的条件语句为 if 时一位读写者 , 一次读写一个字母 . 改为 while 时则不同 .
if (buffer[writeOffset % BUFFER_SIZE] == '#' ) { // 如果有空间可写
index = rand () % 52;
writeOffset = writeOffset % BUFFER_SIZE;
buffer[writeOffset] = letters[index];
printf ( " 第 %2d 位写者写了一个字母 %c /n" , *id,buffer[writeOffset]);
printBuffer();
++writeOffset;
} //WHILE
// 传递出 , 已经生产满的信号 . 然后解锁
//sem_post( &full_sem );
pthread_mutex_unlock (&mutexlock);
}
}
void reader ( int *id) {
while (1) {
sleep (1);
//sem_wait( &full_sem );// 等待写者写好的信号
pthread_mutex_lock (&mutexlock);
//
if (buffer[readOffset % BUFFER_SIZE] != '#' ) { // 有东西 可读
printf ( " 第 %2d 位读者读出了一个字母 %c /n" , *id,buffer[readOffset]);
readOffset = readOffset % BUFFER_SIZE;
buffer[readOffset] = '#' ;
++readOffset;
}
//sem_post( &empty_sem );
pthread_mutex_unlock (&mutexlock);
}
}
int main () {
pthread_t writePID[WRITER]; //2
pthread_t readerPID[READER]; //4
int ret; // ret ~ return value
int i; //for loop
int wID[WRITER]; // 读者与写者的编号 , 因为线程的标识符太长了 , 不好看 .
int rID[READER];
/**
* 初始化同步信号量
*/
ret = sem_init (&empty_sem, 0, BUFFER_SIZE);
if (ret != 0) {
perror ( " sem init failed /n" );
exit (EXIT_FAILURE);
}
ret = sem_init (&full_sem, 0, BUFFER_SIZE);
if (ret != 0) {
perror ( " sem init failed /n" );
exit (EXIT_FAILURE);
}
/**
* 初始化互斥信号量
*/
ret = pthread_mutex_init (&mutexlock,NULL);
if (ret != 0) {
perror ( " mutexlock init failed /n" );
exit (EXIT_FAILURE);
}
/***
* 初始缓冲区的内容
*/
for (i = 0; i < BUFFER_SIZE;i++){
buffer[i] = '#' ;
}
printBuffer(); // 打印开始时候缓冲区的内容 :
/**
* 创建 WRITER(2) 个写者线程
*/
for (i = 0; i < WRITER; i++) {
wID[i] = i+1;
ret = pthread_create (&writePID[i], NULL, ( void *) *writer, &wID[i]);
if (ret != 0) {
perror ( "writer create failed /n" );
exit (EXIT_FAILURE);
}
}
/**
* 创建 READER(4) 个消费者线程
*/
for (i = 0; i < READER; i++) {
rID[i] = i+1;
ret = pthread_create (&readerPID[i], NULL, ( void *) *reader, &rID[i]);
if (ret != 0) {
perror ( "reader create failed /n" );
exit (EXIT_FAILURE);
}
}
/**
* 等待线程完成 .
*/
for (i = 0; i < WRITER; i++) {
pthread_join (writePID[i], NULL);
}
for (i = 0; i < READER; i++) {
pthread_join (readerPID[i], NULL);
}
exit (EXIT_SUCCESS);
}
/***
* 编译运行命令 :
* banxi @ banxi :~/ jeework / pthread $ gcc -g -o rwer ReaderWriter.c - lpthread
*
* 下面是运行结果及分析 : 总共有五不同次测试结果及分析 .
* banxi1@ banxi :~/ jeework / pthread $ ./ rwer
当前缓冲区内容 : # # # # # # # #
第 1 位写者写了一个字母 F
当前缓冲区内容 : F # # # # # # #
第 1 位读者读出了一个字母 F
第 2 位写者写了一个字母 n
当前缓冲区内容 : # n # # # # # #
第 2 位读者读出了一个字母 n
第 1 位写者写了一个字母 C
当前缓冲区内容 : # # C # # # # #
第 3 位读者读出了一个字母 C
第 2 位写者写了一个字母 X
当前缓冲区内容 : # # # X # # # #
第 4 位读者读出了一个字母 X
第 1 位写者写了一个字母 c
当前缓冲区内容 : # # # # c # # #
^C
banxi1988@banxi1988-desktop:~/ jeework / pthread $
( 说明 : 因为程序没有自己终止 , 所以用 CTRL+C 来中断 .)
ret = sem_init( &full_sem , 0, 0);
由于上面对于 full_sem 的初始值为 0, 终止值也是为 0,
所以出现 , 了读出一个写一个的状态 , 而不是写满之后再读完 .
一个读者一次读写一次的话 , 也是程序为了容易观察结果而设定的 . 因为判断是 if. 如果改成 while 就会不同了 .
将在下面对其进行测试 .
但把上面的改为如下的 :
ret = sem_init( &full_sem ,0,BUFFER_SIZE);
时程序运行结果如下 :
banxi1988@banxi1988-desktop:~/ jeework / pthread $ ./ rwer
当前缓冲区内容 : # # # # # # # #
第 1 写者写了一个字母 p
当前缓冲区内容 : p # # # # # # #
第 1 位读者读出了一个字母 p
第 2 写者写了一个字母 X
当前缓冲区内容 : # X # # # # # #
第 2 位读者读出了一个字母 X
第 1 写者写了一个字母 t
当前缓冲区内容 : # # t # # # # #
第 2 写者写了一个字母 t
当前缓冲区内容 : # # t t # # # #
第 4 位读者读出了一个字母 t
第 3 位读者读出了一个字母 t
第 1 写者写了一个字母 M
当前缓冲区内容 : # # # # M # # #
第 2 写者写了一个字母 J
当前缓冲区内容 : # # # # M J # #
第 2 位读者读出了一个字母 M
第 3 位读者读出了一个字母 J
第 1 写者写了一个字母 Y
当前缓冲区内容 : # # # # # # Y #
第 2 写者写了一个字母 m
测试结果三 : 将上面的条件设置 while 时的结果 :
banxi1988@banxi1988-desktop:~/ jeework / pthread $ ./ rwer
当前缓冲区内容 : # # # # # # # #
第 2 位写者写了一个字母 p
当前缓冲区内容 : p # # # # # # #
第 2 位写者写了一个字母 W
当前缓冲区内容 : p W # # # # # #
第 2 位写者写了一个字母 r
当前缓冲区内容 : p W r # # # # #
第 2 位写者写了一个字母 e
当前缓冲区内容 : p W r e # # # #
第 2 位写者写了一个字母 d
当前缓冲区内容 : p W r e d # # #
第 2 位写者写了一个字母 Z
当前缓冲区内容 : p W r e d Z # #
第 2 位写者写了一个字母 G
当前缓冲区内容 : p W r e d Z G #
第 2 位写者写了一个字母 s
当前缓冲区内容 : p W r e d Z G s
第 4 位读者读出了一个字母 p
第 4 位读者读出了一个字母 W
第 4 位读者读出了一个字母 r
第 4 位读者读出了一个字母 e
第 4 位读者读出了一个字母 d
第 4 位读者读出了一个字母 Z
第 4 位读者读出了一个字母 G
第 4 位读者读出了一个字母 s
第 1 位写者写了一个字母 e
当前缓冲区内容 : e # # # # # # #
第 1 位写者写了一个字母 F
当前缓冲区内容 : e F # # # # # #
第 1 位写者写了一个字母 E
当前缓冲区内容 : e F E # # # # #
第 1 位写者写了一个字母 N
当前缓冲区内容 : e F E N # # # #
第 1 位写者写了一个字母 I
当前缓冲区内容 : e F E N I # # #
第 1 位写者写了一个字母 A
当前缓冲区内容 : e F E N I A # #
第 1 位写者写了一个字母 Y
当前缓冲区内容 : e F E N I A Y #
第 1 位写者写了一个字母 U
当前缓冲区内容 : e F E N I A Y U
^C
banxi1988@banxi1988-desktop:~/ jeework / pthread $
运行结果四 : 去掉了待等待 相应信号量的代码 ..
这样的话读写的控制就又 if 或者 while 来判断了 .
这个一来 ,sleep 就必不可少 , 因为只有在一个线程 sleep 和时候 , 其它的线程才有抢占线程的平均的机会 .
为什么说平均呢 , 因为当写满或者读完的时候 , 线程也会释放掉锁的 .
但是马上又加入了抢点锁的过程 .
banxi1988@banxi1988-desktop:~/ jeework / pthread $ ./ sigwrer
当前缓冲区内容 : # # # # # # # #
第 1 位写者写了一个字母 s
当前缓冲区内容 : s # # # # # # #
第 2 位读者读出了一个字母 s
第 2 位写者写了一个字母 h
当前缓冲区内容 : # h # # # # # #
第 1 位读者读出了一个字母 h
第 1 位写者写了一个字母 Y
当前缓冲区内容 : # # Y # # # # #
第 2 位读者读出了一个字母 Y
第 2 位写者写了一个字母 I
当前缓冲区内容 : # # # I # # # #
第 1 位读者读出了一个字母 I
第 1 位写者写了一个字母 D
当前缓冲区内容 : # # # # D # # #
第 2 位读者读出了一个字母 D
第 2 位写者写了一个字母 Q
当前缓冲区内容 : # # # # # Q # #
第 3 位读者读出了一个字母 Q
第 1 位写者写了一个字母 c
当前缓冲区内容 : # # # # # # c #
第 2 位写者写了一个字母 z
^C
banxi1988@banxi1988-desktop:~/ jeework / pthread $
运行结果五 : 这个是将线程的信号量全都注释掉之后的代码 .
发现 , 读写纯种不再是交互执行的了 . 不过还是存在某些规律的 , 比如 , 先写的然后读的 . 这个也许是调度问题吧 ..
banxi1988@banxi1988-desktop:~/ jeework / pthread $ gcc -g -o sigwrer ReaderWriter.c - lpthread
banxi1988@banxi1988-desktop:~/ jeework / pthread $ ./ sigwrer
当前缓冲区内容 : # # # # # # # #
第 2 位写者写了一个字母 M
当前缓冲区内容 : M # # # # # # #
第 1 位写者写了一个字母 m
当前缓冲区内容 : M m # # # # # #
第 1 位读者读出了一个字母 M
第 2 位读者读出了一个字母 m
第 2 位写者写了一个字母 v
当前缓冲区内容 : # # v # # # # #
第 1 位写者写了一个字母 p
当前缓冲区内容 : # # v p # # # #
第 2 位读者读出了一个字母 v
第 1 位读者读出了一个字母 p
第 2 位写者写了一个字母 O
当前缓冲区内容 : # # # # O # # #
第 1 位写者写了一个字母 A
当前缓冲区内容 : # # # # O A # #
第 2 位读者读出了一个字母 O
第 1 位读者读出了一个字母 A
第 2 位写者写了一个字母 y
当前缓冲区内容 : # # # # # # y #
^C
banxi1988@banxi1988-desktop:~/ jeework / pthread $
*
*/