1. 程式人生 > >經典IPC問題實現與思考

經典IPC問題實現與思考

本文介紹多種同步方法來解決程序間通訊問題,並給出模型的程式碼實現。

程式碼介紹

本文主要側重於程式碼實現,若對互斥(mutex)以及死鎖問題還不夠熟悉,可以參考我這篇文章

  include <pthread.h>
   - pthread_mutex_t
     - 用於建立互斥(mutex)變數
   - pthread_mutex_lock
     - 用於對互斥加鎖,防止其他程序(執行緒)進入臨界區。
   - pthread_create
     - 用於建立臨界區
   - pthread_t
     - 標識執行緒```
include <semaphore.h>

   - sem_t
     - 用於建立訊號量變數
   - sem_init
     - 訊號量初始化函式
   - sem_wait
     - 相當於Dijkstra的down操作
   - sem_post
     - 相當於Dijkstra的up操作

哲學家進餐問題

引言

對於問題的描述不過多介紹,可參考wiki

最naive的想法就是哲學家先取左叉子,再取右叉,最後再依次放回就行。但很遺憾這種想法會造成死鎖。想象一下:若在同一時刻所有的哲學家都同時拿起左叉,那他們再也拿不到右叉,導致程式在一直執行卻又無法進行下去。

因此,我們很容易想到利用互斥(mutex)進行改進。我們將哲學家進餐的一系列動作都lock起來,這樣的確能避免死鎖,但這樣也會導致同一時間只有一個程序在進餐(資源浪費),有沒有更好的方法呢?

我們知道,若有n個哲學家,則最多n/2個哲學家同時進餐。我們可以首先利用mutex將取叉的動作lock住,同時檢查周圍的人是否正在進餐。這樣,我們就需要一個數組來標識每個哲學家的狀態。最後,我們把動作分解為take_forks,eat,think,put_forks四個狀態,其中需要特別注意take_forks和put_forks

程式碼

#include <stdio.h>
#include <pthread.h> #include <unistd.h> #include <semaphore.h> #define N 5 #define LEFT (i - 1 + N) % N #define RIGHT (i + 1) % N #define THINKING 0 #define HUNGRY 1 #define EATING 2 int state[N]; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; sem_t s[N]; void think(int i) { if (state[i] == THINKING) { printf("philosopher %d is thinking.......\n", i); sleep(3); } } void eat(int i) { if (state[i] == EATING) { printf("philosopher %d is eating.......\n", i); sleep(3); } } void init() { int i; for (i = 0; i < N; i++) { if (sem_init(&s[i], 1, 1) != 0) { printf("sem_init is wrong\n"); } } } void test(int i) { if (state[i] == HUNGRY && state[LEFT] != EATING && state[RIGHT] != EATING) { state[i] = EATING; sem_post(&s[i]); return; } } void take_forks(int i) { pthread_mutex_lock(&mutex); state[i] = HUNGRY; test(i); pthread_mutex_unlock(&mutex); sem_wait(&s[i]); } void put_forks(int i) { pthread_mutex_lock(&mutex); state[i] = THINKING; test(LEFT); test(RIGHT); pthread_mutex_unlock(&mutex); } void philosopher(int i) { while (1) { think(i); take_forks(i); eat(i); put_forks(i); } } int main() { int i = 0; pthread_t id; int ret; init(); for (i = 0; i < N; i++) { ret = pthread_create(&id, NULL, (void *)philosopher, i); if (ret != 0) { printf("Create pthread error!/n"); return 1; } } pthread_join(id, NULL); }

需要注意,我們的test函式只是測試周圍的哲學家的狀態,若周圍哲學家已經在進餐,則不能拿起叉子,只能等待(63行),直到傳送訊號解除阻塞後才可以進食。

讀者-寫者問題

引言

哲學家進餐問題對於多個競爭程序互斥的訪問有限資源(如I/O裝置)這一類問題的建模十分有用,在讀者-寫者問題中,為資料庫訪問建立了一個模型。

我們知道,對一個成熟的資料庫來說,很重要的一點就是處理多併發請求的問題。在這裡我們規定:允許多個程序同時讀一個數據庫,但不允許讀寫同時進行,也不允許同時寫。

程式碼實現

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>

#define N 5

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
sem_t db; /* control the access of database  */
int rc;   /* the process want to access the database */

void init()
{
    rc = 0;
    if (sem_init(&db, 1, 1) != 0)
    {
        printf("sem_init is wrong\n");
    }
}

void read_data(int i)
{
    printf("person %d is reading.......\n", i);
    sleep(3);
}

void finish_read(int i)
{
    printf("person %d  finish reading!\n", i);
}

void write_data()
{
    printf("person is writing.......\n");
    sleep(5);
}

void finish_write()
{
    printf("person finish writing!\n");
}

void reader()
{
    while (1)
    {
        pthread_mutex_lock(&mutex);
        rc += 1;
        if (rc == 1)
            sem_wait(&db);
        pthread_mutex_unlock(&mutex);

        read_data(rc);

        pthread_mutex_lock(&mutex);
        rc -= 1;
        finish_read(rc);
        if (rc == 0)
            sem_post(&db);
        pthread_mutex_unlock(&mutex);
        sleep(3); /* wait for another reading ... */
    }
}

void writer()
{
    while (1)
    {
        sem_wait(&db);
        write_data();
        finish_write();
        sem_post(&db); /* restore the access */
        sleep(5);      /* wait for another writing ... */
    }
}

int main()
{
    int i = 0;

    pthread_t id1, id2;
    int ret1, ret2;
    init();

    for (i = 0; i < N; i++)
    {
        ret1 = pthread_create(&id1, NULL, (void *)reader, NULL);
        ret2 = pthread_create(&id2, NULL, (void *)writer, NULL);
        if (ret1 != 0 || ret2 != 0)
        {
            printf("Create pthread error!/n");
            return 1;
        }
    }
    pthread_join(id1, NULL);
    pthread_join(id2, NULL);
    return 1;
}

建模方式與以上類似,但要注意,這個模型有一定缺陷。比如,如果讀者的週期小於寫者的週期,那麼系統將一直允許讀者進入,而寫者一直被掛起直到沒有一個讀者為止。這個時候也許就會讓寫者等待很久。

因此,讀者可以自行思考更為合理的建模方式,這種建模方式是根據實際應用需求的,如:

  1. 讀者優先模式
  2. 寫者優先模式
  3. 嚴格按照時間戳讀寫