LinuxSir.cn,穿越时空的Linuxsir!

 找回密码
 注册
搜索
热搜: shell linux mysql
查看: 905|回复: 1

不规则的多进程读些同步问题

[复制链接]
发表于 2009-1-6 10:39:47 | 显示全部楼层 |阅读模式
各位高手:
    我在工作中遇到了一个问题,Linux下的多进程读些同步,说不太规则,是说:教科书上的进程同步,都是读写分别属于不同进程,读进程里只有读,写进程里只有写,但我碰到的情况如下:
两个程序配合
1 甲程序里父进程fork()出的每个子进程都先读共享内存A区,如果在共享内存A区里没找到所需内容所需数据就查找数据库,查到后到了就写到A区共享内存里(次序一定,必定是先读后写),再读写B内存,同样,B区里没找到所需内容所需数据就查找数据库,查到后到了就写到B区共享内存里( 也是先读后写),
子进程1…..n流程如下:
if (-1 == readAmem())        //返回-1表明内存A区里没找到                                       
    writeAmem ();//写A内存                                                
                                                                                                                .
//再接着读另外一块共享内存B                                                .
if(-1==readBmem())// 返回-1表明内存B区里没找到                                                               
     writeBmem();,//写B内存

2 乙程序配合甲程序:乙程序是单个进程,只有写内存,不读,乙程序等待键盘输入,通过手工输入命令行决定写A还是写B内存,一次只输一个命令,即一次只写一块内存,因为不能保证执行写内存时没有甲程序的子进程在读写同一块内存,所以也需要保护措施
                                .
//乙程序通过一个命令行写共享内存A区,   
writeAmem ();//写A内存  
// 输入另外一个命令,则写共享内存B区,   
writeBmem();//写B内存  
         
要求:1甲程序的任意多的子进程可以同时读A或者B内存,
2 一次只有一个进程(这个进程既可以是甲程序的也可能是乙程序的)可以往数据区A写,但同时可以有另外一个进程往另外一个数据区B里写,同理,一次只有一个写进程可以往数据区B写,但同时可以有另外一个进程往另外一个数据区A里写
3 如果一个进程正在往数据区A中写, 禁止任何进程读数据区A,但允许其他进程读另外一个数据区B,也允许另外的子进程写另外一个数据区B

总结:即:各进程在同一块内存区“读-写” 互斥;“写-写” 互斥;“读-读” 允许,但各进程在不同的内存区“读-写”, “写-写”,“读-读”都允许

我参考了线程同步的方法,觉得规则的进程同步方法也适用于这个看似不规则的问题,解决如下:

/*为同步访问访问共享内存A,设置三个互斥信号量:
3 rwmutex        用于写者与其他读者/写者互斥的访问共享内存A
4 rmutex        用于读者互斥的访问读者计数器信号量6
5 nrmutex        用于写者等待已进入读者退出,所有读者退出前互斥写操作
6                                  读者计数器
初始化四个信号量:var   rwmutex,rmutex,nrmutex:semaphore:=1,1,1;6 = 0*/
甲程序的每个子进程里设置同步措施如下:
以下L+数字表示行号

//读共享内存A,即readAmem()函数里:
L1 semop(semid, &p3, 1);    //    P(rwmutex);
L 2        semop(semid, &p4, 1);   //  P(rmutex); 互斥的访问读者计数器信号量6
L 3                semop(semid,&v6,1);// readcount++;
L 4            if (1 == semctl(semid, 6, GETVAL,0)) //if (readcount == 1)
L 5                semop(semid,&p5,1); //P(nrmutex);  //有读者进入,互斥写操作
L 6        semop(semid, &v4, 1);      //  V(rmutex);
L7 semop(semid, &v3, 1); // V(rwmutex);  //及时释放读写互斥信号量,允许其它读、//写进程申请资源读数据;

L 8  fprintf(stderr, " %ld 子进程 READ A SHM................Begin!\n", pid);
                …….
L 9        fprintf(stderr, " %ld 子进程 READA SHM........Over!\n", pid);

L 10        semop(semid, &p4, 1);//       P(rmutex);
L 11                semop(semid,&p6,1);//readcount--;
L 12                if (0 == semctl(semid, 6, GETVAL,0))
L 13                semop(semid,&v5,1); //V(nrmutex);  //所有子进程读完A,允////许写更新
L 14         semop(semid, &v4, 1); //     V(rmutex);

//if (-1 == readAmem())//则进入写A内存函数//writeAmem (),如下:
L 15 semop(semid,&p3,1);//        P(rwmutex);    //互斥后续其它读者、写者
L 16        semop(semid,&p5,1);//        P(nrmutex);    //如有读者正在读A内存,等待所有//读者读完
               
L 17                fprintf(stderr, " %ld 子进程 write A SHM............Begin!\n", pid);
                ……………….
L 18                fprintf(stderr, " %ld 子进程 write A SHM..........Over!\n", pid);//写//更新;
L 19  semop(semid,&v5,1);//V(nrmutex);    //允许后续新的第一个读者进入后//互//斥写操作
L 20 semop(semid,&v3,1);//V(rwmutex);    //允许后续新读者及其它写者
接下来,甲程序的子进程进入读写B内存
同理:对于同步访问B共享内存,方法和同步访问A共享内存原理完全一样
/*设置另外三个互斥信号量:
7 rwmutex        用于写B内存时与其他读者/写者互斥的访问共享内存B
10                                  已在读B内存的子进程计数器
8 rmutex        用于读者互斥的访问读B内存的计数器---信号量10
9 nrmutex        用于写B内存时等待已进入读B内存的所有子进程退出,所有读B退出前互斥写B操作
var   rwmutex,rmutex,nrmutex:semaphore:=1,1,1;10 = 0*/

//读共享内存B,即readBmem()函数里:
L 21 semop(semid, &p7, 1);    //    P(rwmutex);
L 22        semop(semid, &p8, 1); //P(rmutex);用于互斥的访问读者计数器信号量10
L 23                semop(semid,&v10,1);// readcount++;
L 24                if (1 == semctl(semid, 10, GETVAL,0)) //if (readcount == 1)
L 25                   semop(semid,&p9,1); //P(nrmutex);  //有读者进入,互斥写操作
L 26        semop(semid, &v8, 1);      //  V(rmutex);
L 27 semop(semid, &v7, 1);    //   V(rwmutex);  //及时释放读写互斥信号量,允许//其它读、写进程申请资源读数据;

L 28        fprintf(stderr, " %ld 子进程read B SHM...........Begin!\n", pid);
        
L 29        fprintf(stderr, " %ld 子进程read B SHM...........Over!\n", pid);

L 30        semop(semid, &p8, 1);//       P(rmutex);
L 31                semop(semid,&p10,1);//readcount--;
L 32                if (0 == semctl(semid, 10, GETVAL,0))   // if(readcount == 0)
L 33                        semop(semid,&v9,1); //V(nrmutex);  //所有读者退出,允许写更新
L 34         semop(semid, &v8, 1); //     V(rmutex);

//if (-1 == readBmem())/则进入写共享内存函数//writeBmem()
L 35 semop(semid,&p7,1);//        P(rwmutex);    //互斥后续其它读/写B内存
L 36    semop(semid,&p9,1);//        P(nrmutex);    //如有读B内存的子进程//正在读,等待所有读者读完

L 37        fprintf(stderr, " %ld 子进程write B SHM..........Begin!\n", pid);
        。。。。。。
L 38        fprintf(stderr, " %ld 子进程write B SHM...........Over!\n", pid);

L 39          semop(semid,&v9,1);//V(nrmutex); //允许后续新的第一个读者进入后互斥写B内存操作
L 40        semop(semid,&v7,1);//V(rwmutex); //允许后续新读者及其它写者

以上是甲程序的子进程,对于乙程序,等待手工输入的命令行刷新A或者B共享内存,写A内存时同步措施同甲程序的writeAmem()中的一样,如下L41---L46
L41 semop(semid,&p3,1);//        P(rwmutex);    //互斥后续其它读者、写者
L 42        semop(semid,&p5,1);//        P(nrmutex);    //如有读者正在读A等待所有//读者读完
               
L43        fprintf(stderr, " 乙进程 write A SHM............Begin!\n");
                ……………….
L 44        fprintf(stderr, "乙进程 write A SHM..........Over!\n");//写//更新;
L 45  semop(semid,&v5,1);//V(nrmutex);    //允许后续新的第一个读者进入后//互//斥写操作
L 46 semop(semid,&v3,1);//V(rwmutex);    //允许后续新读者及其它写者


同理:乙程序刷新B内存时同步措施同writeBmem()中的一样,如下L51---L56
L51 semop(semid,&p7,1);//        P(rwmutex);    //互斥后续其它读/写B内存
L52    semop(semid,&p9,1);//        P(nrmutex);    //如有读B内存的子进程//正在读,等待所有读者读完

L53        fprintf(stderr, "乙进程 write B SHM ..........Begin!\n", pid);
        。。。。。。
L 54        fprintf(stderr, "乙进程 write B SHM...........Over!\n", pid);

L55          semop(semid,&v9,1);//V(nrmutex); //允许后续新的第一个读者进入后互斥写8583field内存操作
L 56        semop(semid,&v7,1);//V(rwmutex); //允许后续新读者及其它写者

几种进程同步次序情景分析:
情景一:
1 设甲程序开的子进程1先运行完L1行, 3#信号量由1变为0,接着往下运行,在子进程1运行完L7之前,此时若乙程序运行L41行,或是再有子进程2….n再运行L1行,则都将使3#信号量变为负数,则乙程序被阻塞在L41行,甲程序的子进程都将依次被阻塞在readAmem()函数的L1行,系统依次将被阻塞的子进程的标志放入3#信号量的阻塞队列里(队列,意味着唤醒时先进者先出),

2 子进程1在L2—L6的操作中,使得表示正在读A内存的子进程的计数器---6#信号量加1,5#信号量的值由1变为0,表明有子进程正在读A内存,子进程1做完L7行,使3#信号量的值加1,唤醒3#信号量的阻塞队列里下一个进程,假设下一个进程是乙程序的L42行,再有子进程企图运行L1行,都一律被3#信号量堵塞,此时有乙程序和子进程1两个进程都是活跃的,有两种可能
A 假设系统安排先继续运行乙程序运行到第L42行,5#信号量的值由0变为-1,还没来得及做L43写A内存再次被阻塞,乙进程的标志放入5#信号量的阻塞队列里,
子进程1继续运行L8-L9读A内存,读完后在L14行唤醒乙进程,乙进程写完A内存后,L46行v3操作唤醒下一个3#信号量的阻塞队列里的进程

B 假设系统安排先继续运行子进程1的L8-L9读完A内存,只要子进程1先做完L13的 V5操作 ,使得5#信号量由0变1,则系统再调度运行乙进程第L42行,P5操作,乙进程不再堵塞,写A内存,但此时不再有子进程读A内存,不会冲突,反之,如果乙程序L42行先于子进程1的L13的 V5操作,则乙写B内存的进程被堵塞

情景二:
1 如果乙程序写A内存的进程的L41的P3操作抢在子进程1的L1之前先做,3#信号量由1变为0,接着往下运行,在乙运行完L46之前,此时若再有子进程2….n再运行L1行,子进程都将依次被阻塞在readAmem()函数的L1行,直至乙程序写完A内存后运行完L46,唤醒子进程

情景三:
上面两种情境都考虑了子进程运行时有人手工输入命令而启动的写内存的乙程序的参与,如果子进程启动时没有乙程序的打扰,
1 设甲程序开的子进程1先运行完L1, 3#信号量由1变为0,接着往下运行,在子进程1运行完L7之前,再有子进程2….n再运行L1行,则都将使3#信号量变为负数,子进程都将依次被阻塞在readAmem()函数的L1行,系统依次将被阻塞的子进程的标志放入3#信号量的阻塞队列里(队列,意味着唤醒时先进者先出),
2 子进程1做完L7行,使3#信号量的值加1,唤醒3#信号量的阻塞队列里下一个子进程2,此时两个子进程都是活跃的,同理,子进程2做完L7行,使3#信号量的值加1,唤醒3#信号量的阻塞队列里下一个子进程3(如果子进程3存在),此时3个子进程都是活跃的,………则这表明n 个子进程同时读A内存是允许的,同时读A内存的子进程个数由每个子进程的L3行统计
3 一旦有某子进程读完,就在L11减少读A内存的子进程个数
4一旦有某子进程读完率先进入writeAmem() 函数, 运行L15行P3操作,此时情况等同于情境一第一步,只不过情境一里的乙进程被现在的某子进程的writeAmem() 函数代替,可参阅情景一
  

至于另外一个共享内存B,因为其读/写与A内存完全不存在同步的必要,则可换另外一套信号量7-10,步骤完全一样


不知各位高人看出我的解决方案有什么疏漏吗?请不吝指正!多谢!
发表于 2009-1-7 04:41:13 | 显示全部楼层
参考boost.thread的例子即可
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表