linux网络编程之System V 信号量(二):用信号量实现进程互斥示例和解决哲学家就餐问题

一、我们在前面讲进程间通信的时候提到过进程互斥的概念,下面写个程序来模拟一下,程序流程如下图:

linux网络编程之System V 信号量(二):用信号量实现进程互斥示例和解决哲学家就餐问题_第1张图片

即父进程打印字符O,子进程打印字符X,每次打印一个字符后要sleep 一下,这里要演示的效果是,在打印程序的边界有PV操作,故每个进程中间sleep 的时间即使时间片轮转到另一进程,由于资源不可用也不会穿插输出其他字符,也就是说O或者X字符都会是成对出现的,如OOXXOOOOXXXXXXOO....

程序如下:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include
#include
#include
#include
#include
#include

#define ERR_EXIT(m) \
     do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    }  while( 0)

union semun
{
     int val;     /* Value for SETVAL */
     struct semid_ds *buf;     /* Buffer for IPC_STAT, IPC_SET */
     unsigned  short  *array;   /* Array for GETALL, SETALL */
     struct seminfo  *__buf;   /* Buffer for IPC_INFO (Linux-specific) */
};

int semid;
/* pv操作之间的临界区,导致打印的字符一定是成对出现的 */
void print( char op_char)
{
     int pause_time;
    srand(getpid());
     int i;
     for (i =  0; i <  10; i++)
    {
        sem_p(semid);
        printf( "%c", op_char);
        fflush(stdout);
        pause_time = rand() %  3;
        sleep(pause_time);
        printf( "%c", op_char);
        fflush(stdout);
        sem_v(semid);
        pause_time = rand() %  2;
        sleep(pause_time);
    }

}

int main( void)
{

    semid = sem_create(IPC_PRIVATE);
    sem_setval(semid,  1);
    pid_t pid;
    pid = fork();
     if (pid == - 1)
        ERR_EXIT( "fork");

     if (pid >  0)
    {

        print( 'o');
        wait( NULL);
        sem_d(semid);

    }

     else
    {

        print( 'x');

    }


     return  0;
}

sem_create 等函数参考工具集。在调用semget 时指定key = IPC_PRIVATE,表示创建的是私有的信号量集,但具有亲缘关系的进程是可见的,比如父子进程。输出如下:

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./print 
ooxxooxxooxxooxxooooooxxooxxooxxooxxxxxx

可以看到输出都是成对出现的字符。

分析一下:semval = 1,假设父进程先被调度执行,父进程先P了一下,此时 semval = 0,子进程在父进程睡眠时间被调度的时候尝试P,semval = -1,然后子进程阻塞了,父进程打印完V了一下,semval = 0,唤醒子进程,子进程的P操作返回,打印字符睡眠后V了一下,semval = 1。当然在子进程睡眠的时候父进程可能也在尝试P,故就一直循环往复下去。


二、哲学家就餐问题的描述可以参考这里,下面我们尝试解决这个问题的方法是:仅当一个哲学家两边筷子都可用时才允许他拿筷子。

linux网络编程之System V 信号量(二):用信号量实现进程互斥示例和解决哲学家就餐问题_第2张图片

上图中红色数字表示哲学家的编号,总共5个哲学家,用5个进程来表示;黑色数字表示筷子的编号,总共有5根筷子,可以定义一个信号量集中含有5个信号量,每个信号量的初始值为1,当某个哲学家可以同时得到两根筷子(同时P两个信号量返回)时可以用餐,否则阻塞等待中。用餐后需要同时V一下两个信号量,让其他进程可以P成功。

程序如下:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define ERR_EXIT(m) \
     do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    }  while( 0)

union semun
{
     int val;     /* Value for SETVAL */
     struct semid_ds *buf;     /* Buffer for IPC_STAT, IPC_SET */
     unsigned  short  *array;   /* Array for GETALL, SETALL */
     struct seminfo  *__buf;   /* Buffer for IPC_INFO (Linux-specific) */
};

int semid;

#define DELAY (rand() %  5 +  1)

void wait_for_2fork( int no)
{
     int left = no;
     int right = (no +  1) %  5;

     struct sembuf buf[ 2] =
    {
        {left, - 10},
        {right, - 10}
    };

    semop(semid, buf,  2);
}

void free_2fork( int no)
{
     int left = no;
     int right = (no +  1) %  5;

     struct sembuf buf[ 2] =
    {
        {left,  10},
        {right,  10}
    };

    semop(semid, buf,  2);
}

void philosopere( int no)
{
    srand(getpid());
     for (; ;)
    {

        printf( "%d is thinking\n", no);
        sleep(DELAY);
        printf( "%d is hungry\n", no);
        wait_for_2fork(no);
        printf( "%d is eating\n", no);
        sleep(DELAY);
        free_2fork(no);
    }
}


int main( void)
{

    semid = semget(IPC_PRIVATE,  5, IPC_CREAT |  0666);
     if (semid == - 1)
        ERR_EXIT( "semget");
     union semun su;
    su.val =  1;
     int i;
     for (i =  0; i <  5; i++)
    {
        semctl(semid, i, SETVAL, su);
    }

     int no =  0;
    pid_t pid;
     for (i =  1; i <  5; i++)
    {
        pid = fork();
         if (pid == - 1)
            ERR_EXIT( "fork");

         if (pid ==  0)
        {
            no = i;
             break;
        }
    }

    philosopere(no);

     return  0;
}

我们在前面说过,当需要对一个信号量集中的多个信号量操作时,要么全部执行,要么全部不执行,即是一个原子操作,某个进程需要等待两根筷子,即对两个信号量同时P成功才可以用餐,信号量的序号是0~4,可看作筷子的编号,此时semop 函数操作的是2个信号量,即需定义2个struct sembuf 结构体成员的数组 struct sembuf buf[2]; 

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./dinning 
0 is thinking
3 is thinking
2 is thinking
4 is thinking
1 is thinking
4 is hungry
4 is eating
0 is hungry
3 is hungry
1 is hungry
1 is eating
2 is hungry
3 is eating
4 is thinking
1 is thinking
0 is eating
4 is hungry
0 is thinking
1 is hungry
1 is eating
3 is thinking
4 is eating
0 is hungry
1 is thinking
2 is eating
0 is eating
4 is thinking
2 is thinking
1 is hungry
3 is hungry
3 is eating

0 is thinking
2 is hungry
1 is eating
4 is hungry

................

如果发现程序没有运行卡着,即没有发生死锁现象,从中也可以发现同时最多只能有两个哲学家一起用餐,也不会出现相邻哲学家一起用餐的情况。


参考:

《UNP》

你可能感兴趣的:(linux,环境网络编程)