簡體   English   中英

二進制信號量程序具有極其不可預測的輸出

[英]Binary Semaphore program has extremely unpredictable output

我的程序使用枚舉作為信號量。 有兩種可能的值/狀態(因為它是二進制信號量)。 該程序編譯正常。 signal() 和 wait() 看起來合乎邏輯。 為什么程序行為如此不可預測? 即使是整數 printf 也是錯誤的。 這是代碼:

#include <stdio.h>
#include <pthread.h>
typedef enum {IN_USE,NOT_IN_USE} binary_semaphore;
binary_semaphore s=NOT_IN_USE;
struct parameters{
    int thread_num;
};
void wait(){
    while(s==IN_USE);
    s=IN_USE;
}
void signal(){
    s=NOT_IN_USE;
}
void resource(void *params){
    //assuming parameter is a parameters struct.
    struct parameters *p=(struct parameters*)params;
    wait();
    printf("Resource is being used by thread %d\n",(*p).thread_num);
    signal();
}
int main(void){
    pthread_t threads[4];
    struct parameters ps[4]={{1},{2},{3},{4}};
    register int counter=0;
    while(counter++<4){
        pthread_create(&threads[counter],NULL,resource,(void*)&ps[counter]);
    }
    return 0;
}

我的代碼有什么問題? 一些輸出(是的,每次都不同):-

(NOTHING)
Resource is being used by thread 32514
Resource is being used by thread 0
Resource is being used by thread 0
Resource is being used by thread 32602
Resource is being used by thread -24547608

是不是垃圾值問題?

您遇到了競爭條件以及未定義的行為。 當您在多線程應用程序中使用常規int作為信號量時,無法保證一個進程會讀取變量的值並在另一個進程能夠讀取它之前修改它,這就是為什么您必須使用為您的操作系統設計的並發庫. 此外,您傳遞給pthread_create的函數指針不是正確的類型,這是未定義的行為。

我用指向pthread_mutex_t的指針替換了您的“信號量” enum ,該指針在main()的堆棧上初始化,並且每個線程都獲得一個指向它的指針作為其struct parameter的成員。

我還將void resource(void* params)的定義更改為void* resource(void *params) ,因為這是一個與pthread_create期望的第三個參數相匹配的原型。

您的wait()signal()函數可以用您已經包含的pthread.h中的pthread_mutex_lock()pthread_mutex_unlock()一對一替換。

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

struct parameters{
    int thread_num;
    pthread_mutex_t *mutex; //mutex could be global if you prefer
};

void* resource(void *params){ //pthread_create expects a pointer to a function that takes a void* and returns a void*
    //assuming parameter is a parameters struct.
    struct parameters *p = (struct parameters*)params;
    pthread_mutex_lock(p->mutex); 
    printf("Resource is being used by thread %d\n", p->thread_num);
    pthread_mutex_unlock(p->mutex);
    return NULL;
}

int main(void){
    pthread_t threads[4];
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex, NULL);
    struct parameters ps[4]={{1, &mutex},{2, &mutex},{3, &mutex},{4, &mutex}};
    for(int counter = 0; counter < 4; ++counter)
        pthread_create(&threads[counter], NULL, resource, &ps[counter]);
    //Threads should be joined
    for(int counter = 0; counter < 4; ++counter)
        pthread_join(threads[counter], NULL);
}

這將消除您正在經歷的隨機性。

我的代碼有什么問題?

很多事情,已經在評論中進行了廣泛討論。 最重要的一個是您的代碼充斥着數據競爭。 這是信號量經常用來防止的事情之一,但在這種情況下,是你的信號量本身是活潑的。 未定義的行為結果。

其他問題包括

  • pthreads 線程函數必須返回void * ,但你的返回void
  • 您超出了main()psthreads數組的范圍
  • 在程序退出之前你不加入你的線程。
  • 您可以定義與 C 標准庫函數 ( signal() ) 和標准 POSIX 函數 ( wait() ) 同名的函數。

盡管如此,如果您的 C 實現支持原子選項,那么您可以使用它來實現一個與原始代碼沒有太大區別的工作信號量:

#include <stdio.h>
#include <pthread.h>
#include <stdatomic.h>

// This is used only for defining the enum constants
enum sem_val { NOT_IN_USE, IN_USE };

// It is vital that sem be *atomic*.
// With `stdatomic.h` included, "_Atomic int" could also be spelled "atomic_int".
_Atomic int sem = ATOMIC_VAR_INIT(NOT_IN_USE);

struct parameters{
    int thread_num;
};

void my_sem_wait() {
    int expected_state = NOT_IN_USE;

    // See discussion below
    while (!atomic_compare_exchange_strong(&sem, &expected_state, IN_USE)) {
        // Reset expected_state
        expected_state = NOT_IN_USE;
    }
}

void my_sem_signal() {
    // This assignment is performed atomically because sem has atomic type
    sem = NOT_IN_USE;
}

void *resource(void *params) {
    //assuming parameter is a parameters struct.
    struct parameters *p = params;

    my_sem_wait();
    printf("Resource is being used by thread %d\n", p->thread_num);
    my_sem_signal();

    return NULL;
}

int main(void) {
    pthread_t threads[4];
    struct parameters ps[4] = {{1},{2},{3},{4}};

    for (int counter = 0; counter < 4; counter++) {
        pthread_create(&threads[counter], NULL, resource, &ps[counter]);
    }
    // It is important to join the threads if you care that they run to completion
    for (int counter = 0; counter < 4; counter++) {
        pthread_join(threads[counter], NULL);
    }
    return 0;
}

其中大部分內容非常簡單,但my_sem_wait()函數有更多解釋。 它使用原子比較和交換來確保線程只有在將信號量的值從NOT_IN_USEIN_USE時才會繼續,比較和條件賦值作為單個原子單元執行。 具體來說,這...

    atomic_compare_exchange_strong(&sem, &expected_state, IN_USE)

...說“原子地,將sem的值與expected_state的值進行比較,如果它們比較相等,則將值IN_USE分配給sem 。” 如果 expected_state 不同,該函數還會將expected_state的值設置為從sem中讀取的值,並返回執行的相等比較的結果(等效地:如果指定值已分配給sem ,則返回 1,否則返回 0)。

必須將比較和交換作為一個原子單元來執行。 單獨的原子讀取和寫入將確保沒有數據競爭,但它們不能確保正確的程序行為,因為等待信號量的兩個線程都可以同時看到它可用,每個線程都有機會標記它不可用。 然后兩者都會繼續。 原子比較和交換可防止一個線程在另一個線程讀取和更新該值之間讀取信號量的值。

但是請注意,與 pthreads 互斥鎖或 POSIX 信號量不同,此信號量等待忙碌 這意味着等待獲取信號量的線程在獲取信號量時會消耗 CPU,而不是像等待 pthreads 互斥量的線程那樣進入休眠狀態。 如果信號量訪問通常是無競爭的,或者如果線程永遠不會將其鎖定很長時間,那么這可能沒問題,但在其他情況下,它會使您的程序比需要的更需要資源。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM