【问题标题】:How to create Semaphores in c?如何在c中创建信号量?
【发布时间】:2020-05-30 03:29:38
【问题描述】:

我正在尝试重新创建一个“黑盒”库。在我的 CS 课上,当我们应该使用信号量时(在我们的纸上决赛中),我们得到了一个“sem.h”文件。有 3 个函数,一个用于创建具有初始令牌数量的新信号量,一个用于从信号量中取出令牌,另一个用于将令牌放入信号量中。在 0 时,任何使用阻塞函数的线程都必须等待一个令牌。

为了更好地理解,我一直在尝试根据一些要求实现单一功能的考试重新创建这个 sem.h 和 sem.c。因为这一切都是为了在纸上完成,所以它不会编译,但我觉得很接近

sem.h

typedef struct SEM SEM;
struct SEM *semCreate(int);
void P(struct SEM*); //tokens--
void V(struct SEM*); //tokens++

sem.c

#include "sem.h"
#include <pthread.h>
#include <errno.h>
typedef struct SEM{
    volatile int val; //number of tokens
    pthread_mutex_t m;
    pthread_cond_t c;
}SEM;


/*create new semaphore with #initVal tokens */
SEM *semCreate(int initVal){
    static SEM *sem ={
        .val=initVal
    };
    errno = 0;
    if((errno = pthread_mutex_init(&sem->m,NULL))!=0){
        return NULL;
    }
    if((errno = pthread_cond_init(&sem->c,NULL))!=0){
        return NULL;
    }
    return sem;
}
//take a token from the semaphore
void P(SEM *sem){
    if((errno = pthread_mutex_lock(&sem->m))!=0){
        return;
    }
    while(sem->val <=0){
        if((errno=pthread_cond_wait(&sem->c,&sem->m))!=0){
            pthread_mutex_unlock(&sem->m);
            return;
        }
        sem->val--;
        if(errno = pthread_mutex_unlock(&sem->m)!=0)return;
    }
}
//put a token into the semaphore
void V(SEM *sem){
    if((errno = pthread_mutex_lock(&sem->m))!=0){
        return;
    }

    sem-> val++;

    if((errno = pthread_cond_broadcast(&sem->c))!=0)return;
    if((errno=pthread_mutex_unlock(&sem->m)!=0)) return;
}


如果不清楚这是做什么用的: 这些函数应该限制有多少线程可以同时访问一段代码 例子

//global
static SEM *sem = semCreate(1);
/.../
//critical segment in threadfunction
P(sem);
doReadAndWriteGlobalList();
V(sem);

一旦第一个线程通过 P(),任何后续的 P 调用都无法通过它,直到 V 在同一个 sem 上被调用

编译时出现以下错误:

sem.c: In function ‘semCreate’:
sem.c:14:3: error: field name not in record or union initializer
   .val=initVal
   ^
sem.c:14:3: note: (near initialization for ‘sem’)
sem.c:14:8: error: initialization makes pointer from integer without a cast [-Werror=int-conversion]
   .val=initVal
        ^~~~~~~
sem.c:14:8: note: (near initialization for ‘sem’)
sem.c:14:8: error: initializer element is not constant
sem.c:14:8: note: (near initialization for ‘sem’)
cc1: all warnings being treated as errors

【问题讨论】:

  • FWIW: (1) semCreate() 返回 NULL 表示失败,但你没有整理:如果 pthread_mutex_init() 失败,你应该 free() SEM 对象 malloc() ed(假设你有);如果pthread_cond_init() 失败,您应该pthread_mutex_destroy(),为了完整性,然后free();并且您需要延迟设置 errno 直到您真正返回。 (2)P()没有错误返回,所以调用者无法知道它失败了; errno 也设置得太早了。 (3) V() 同样,并且它可以返回仍然持有互斥锁!错误处理是 hard :-(
  • 请不要从问题中删除问题。如果您想回答自己的问题,请将其作为答案发布。

标签: c struct pthreads semaphore


【解决方案1】:

您不想要静态变量。您想在每次调用 semCreate 时创建一个新对象(内存分配)。因此,

static SEM *sem ={
    .val=initVal
};

应该是

SEM *sem = malloc(sizeof(SEM));
sem->val = initVal;

完成后不要忘记释放信号量。这包括错误!

SEM *semCreate(int initVal){
    SEM *sem = malloc(sizeof(SEM));
    if (!sem)
        goto Error1;

    sem->val = initVal;

    errno = pthread_mutex_init(&sem->m, NULL);
    if (!errno)
       goto Error2;

    errno = pthread_cond_init(&sem->c, NULL);
    if (!errno)
       goto Error3;

    return sem;

Error3:
    pthread_mutex_destroy(&sem->m);
Error2:
    free(buf);
Error1:
    return NULL;
}

除此之外,您的代码还有多个问题。总之,P 是完全错误的。

  • P 可以使用解锁的互斥锁调用 pthread_cond_signal
  • P 可以在互斥锁仍然锁定的情况下返回。
  • P 在非正数时递减该值,而当它假定为正数时递减它。

还有一个问题是PV 执行毫无意义甚至有害的错误处理。如果广播失败,跳过解锁互斥锁?是的,我们不要那样做。

让我们从一个基本的解决方案开始,一个不考虑安全性或效率的解决方案。

void V(SEM *sem) {
   ++sem->val;
}

void P(SEM *sem) {
   // Wait for the semaphore to have a positive value.
   while (sem->val < 1) {
      // This is where another thread could change sem->val.
   }

   --sem->val;
}

现在,让我们通过互斥使其成为线程安全的。

void V(SEM *sem) {
   pthread_mutex_lock(&sem->m);
   ++sem->val;
   pthread_mutex_unlock(&sem->m);
}

void P(SEM *sem) {
   pthread_mutex_lock(&sem->m);

   // Wait for the semaphore to have a positive value.
   while (sem->val < 1) {
      pthread_mutex_unlock(&sem->m);
      // This is where another thread could change sem->val.
      pthread_mutex_lock(&sem->m);
   }

   --sem->val;
   pthread_mutex_unlock(&sem->m);
}

但这是一个忙碌的等待。让我们使用条件变量来休眠,直到信号量发生变化。 (请记住,cond_wait 在进入时解锁提供的互斥锁,并在返回之前重新锁定它。)

void V(SEM *sem) {
   pthread_mutex_lock(&sem->m);

   ++sem->val;

   // Wake up a thread that's waiting, if any.
   if (sem->val > 0)
      pthread_cond_signal(&sem->c);

   pthread_mutex_unlock(&sem->m);
}

void P(SEM *sem) {
   pthread_mutex_lock(&sem->m);

   // Wait for the semaphore to have a positive value.
   while (sem->val < 1)
      pthread_cond_wait(&sem->c, &sem->m);

   --sem->val;

   // Wake up a thread that's waiting, if any.
   if (sem->val > 0)
      pthread_cond_signal(&sem->c);

   pthread_mutex_unlock(&sem->m);
}

多田!


注意事项:

  • 调用pthread_cond_broadcast 毫无意义,因为一次只能修改一个线程。通过在适当的时候让VP 调用pthread_cond_signal,我们可以避免白白唤醒线程。
  • 我们可以省略检查 pthread_mutex_lockpthread_mutex_unlockpthread_cond_signal 是否在工作代码中失败,因为它们只会因编码错误而失败。

【讨论】:

  • 非常感谢,它解决了我的编译器错误。测试的解决方案是不使用静态但也不使用malloc,但我认为返回局部变量的地址是不对的
  • Re "我认为返回局部变量的地址是不对的",确实。
  • 一周后在这里发表评论,我将分享一个很好的干净工作解决方案以及解释。 (已经写好了)
  • 添加了解释的解决方案。
  • 非常感谢,这真的帮助我理解了为什么要做的事情:)
【解决方案2】:

你的采购是错误的。 在循环内部,您释放互斥锁;这会使循环顶部的测试和条件等待都无效。

你可能想要这样的东西:

   lock(&s->mutex);
   while (s->val <= 0) {
        wait(&s->cv, &s->mutex);
   }
   s->val--;
   unlock(&s->mutex);

从条件变量中唤醒只是表明您应该重新检查您的条件;不是断言你的情况已经到来。

【讨论】:

    猜你喜欢
    • 2015-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-09
    • 1970-01-01
    • 2022-12-01
    • 2015-04-24
    相关资源
    最近更新 更多