繁体   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