linux網路編程之System V 訊號量(二):用訊號量實現進程互斥樣本和解決哲學家就餐問題

來源:互聯網
上載者:User

一、我們在前面講處理序間通訊的時候提到過進程互斥的概念,下面寫個程式來類比一下,程式流程如:


即父進程列印字元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<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/wait.h>

#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,故就一直迴圈往複下去。


二、哲學家就餐問題的描述可以參考這裡,下面我們嘗試解決這個問題的方法是:僅當一個哲學家兩邊筷子都可用時才允許他拿筷子。


中紅色數字表示哲學家的編號,總共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<stdio.h>
#include<stdlib.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/types.h>
#include<unistd.h>
#include<errno.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/wait.h>

#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, -1, 0},
        {right, -1, 0}
    };

    semop(semid, buf, 2);
}

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

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

    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》

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.