程式間通訊如何加鎖

roc_guo發表於2022-02-15

程式間通訊如何加鎖程式間通訊如何加鎖

關於程式間的通訊方式估計大多數人都知道,這也是常見的面試八股文之一。

個人認為這種面試題沒什麼意義,無非就是答幾個關鍵詞而已,更深入的可能面試官和麵試者都不太瞭解。

關於程式間通訊方式和優缺點我之前在【這篇文章】中有過介紹,感興趣的可以移步去看哈。

程式間通訊有一種[共享記憶體]方式,大家有沒有想過,這種通訊方式中如何解決資料競爭問題?

我們可能自然而然的就會想到用鎖。但我們平時使用的鎖都是用於解決執行緒間資料競爭問題,貌似沒有看到過它用在程式中,那怎麼辦?

我找到了兩種方法,訊號量和互斥鎖。

直接給大家貼程式碼吧,首先是訊號量方式:

#include#include#include#include#include#include#include#include#include#includeconstexpr int kMappingSize = 4096;
void sem() {
    const char* mapname = "/mapname";
    int mapfd = shm_open(mapname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    MEOW_DEFER {
        if (mapfd > 0) {
            close(mapfd);
            mapfd = 0;
        }
        shm_unlink(mapname);
    };
    if (mapfd == -1) {
        perror("shm_open failed \n");
        exit(EXIT_FAILURE);
    }
    if (ftruncate(mapfd, kMappingSize) == -1) {
        perror("ftruncate failed \n");
        exit(EXIT_FAILURE);
    }
    void* sp = mmap(nullptr, kMappingSize, PROT_READ | PROT_WRITE, MAP_SHARED, mapfd, 0);
    if (!sp) {
        perror("mmap failed \n");
        exit(EXIT_FAILURE);
    }
    sem_t* mutex = (sem_t*)sp;
    if (sem_init(mutex, 1, 1) != 0) {
        perror("sem_init failed \n");
        exit(EXIT_FAILURE);
    }
    MEOW_DEFER { sem_destroy(mutex); };
    int* num = (int*)((char*)sp + sizeof(sem_t));
    int cid, proc_count = 0, max_proc_count = 8;
    for (int i = 0; i < max_proc_count; ++i) {
        cid = fork();
        if (cid == -1) {
            perror("fork failed \n");
            continue;
        }
        if (cid == 0) {
            sem_wait(mutex);
            (*num)++;
            printf("process %d : %d \n", getpid(), *num);
            sem_post(mutex);
            if (munmap(sp, kMappingSize) == -1) {
                perror("munmap failed\n");
            }
            close(mapfd);
            exit(EXIT_SUCCESS);
        }
        ++proc_count;
    }
    int stat;
    while (proc_count--) {
        cid = wait(&stat);
        if (cid == -1) {
            perror("wait failed \n");
            break;
        }
    }
    printf("ok \n");
}

程式碼中的MEOW_DEFER我在之前的RAII相關文章中介紹過,它內部的函式會在生命週期結束後觸發。它的核心函式其實就是下面這四個:

int sem_init(sem_t *sem,int pshared,unsigned int value);
int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_destroy(sem_t *sem);

具體含義大家應該看名字就知道,這裡的重點就是sem_init中的pshared引數,該引數為1表示可在程式間共享,為0表示只在程式內部共享。

第二種方式是使用鎖,即pthread_mutex_t,可是pthread_mutex不是用作執行緒間資料競爭的嗎,怎麼能用在程式間呢?

我也是最近才知道,可以給它配置一個屬性,示例程式碼如下:

pthread_mutex_t* mutex;
pthread_mutexattr_t mutexattr;
pthread_mutexattr_init(&mutexattr);
pthread_mutexattr_setpshared(&mutexattr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(mutex, &mutexattr);

它的預設屬性是程式內私有,但是如果給它配置成PTHREAD_PROCESS_SHARED,它就可以用在程式間通訊中。

完整程式碼如下:

void func() {
    const char* mapname = "/mapname";
    int mapfd = shm_open(mapname, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    MEOW_DEFER {
        if (mapfd > 0) {
            close(mapfd);
            mapfd = 0;
        }
        shm_unlink(mapname);
    };
    if (mapfd == -1) {
        perror("shm_open failed \n");
        exit(EXIT_FAILURE);
    }
    if (ftruncate(mapfd, kMappingSize) == -1) {
        perror("ftruncate failed \n");
        exit(EXIT_FAILURE);
    }
    void* sp = mmap(nullptr, kMappingSize, PROT_READ | PROT_WRITE, MAP_SHARED, mapfd, 0);
    if (!sp) {
        perror("mmap failed \n");
        exit(EXIT_FAILURE);
    }
    pthread_mutex_t* mutex = (pthread_mutex_t*)sp;
    pthread_mutexattr_t mutexattr;
    pthread_mutexattr_init(&mutexattr);
    pthread_mutexattr_setpshared(&mutexattr, PTHREAD_PROCESS_SHARED);
    pthread_mutex_init(mutex, &mutexattr);
    MEOW_DEFER {
        pthread_mutexattr_destroy(&mutexattr);
        pthread_mutex_destroy(mutex);
    };
    int* num = (int*)((char*)sp + sizeof(pthread_mutex_t));
    int cid, proc_count = 0, max_proc_count = 8;
    for (int i = 0; i < max_proc_count; ++i) {
        cid = fork();
        if (cid == -1) {
            perror("fork failed \n");
            continue;
        }
        if (cid == 0) {
            pthread_mutex_lock(mutex);
            (*num)++;
            printf("process %d : %d \n", getpid(), *num);
            pthread_mutex_unlock(mutex);
            if (munmap(sp, kMappingSize) == -1) {
                perror("munmap failed\n");
            }
            close(mapfd);
            exit(EXIT_SUCCESS);
        }
        ++proc_count;
    }
    int stat;
    while (proc_count--) {
        cid = wait(&stat);
        if (cid == -1) {
            perror("wait failed \n");
            break;
        }
    }
    printf("ok \n");
}

我想這兩種方式應該可以滿足我們日常開發過程中的大多數需求。

鎖的方式介紹完之後,可能很多朋友自然就會想到原子變數,這塊我也搜尋了一下。但是也不太確定C++標準中的atomic是否在程式間通訊中有作用,不過看樣子boost中的atomic是可以用在程式間通訊中的。

其實在研究這個問題的過程中,還找到了一些很多解決辦法,包括:

  1. Disabling Interrupts
  1. Lock Variables
  1. Strict Alternation
  1. Peterson's Solution
  1. The TSL Instruction
  1. Sleep and Wakeup
  1. Semaphores
  1. Mutexes
  1. Monitors
  1. Message Passing
  1. Barriers

這裡就不過多介紹啦,大家感興趣的可以自行查閱資料哈。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69901823/viewspace-2855681/,如需轉載,請註明出處,否則將追究法律責任。

相關文章