如何在c中创建信号量?
How to create Semaphores in c?
我正在尝试重新创建一个 "Blackbox" 库。在我的 CS class 中,当我们应该使用信号量时(在我们的纸上决赛中),我们得到了一个 "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 调用将无法通过它,直到在同一个 sem 上调用 V
编译时出现以下错误:
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
您不需要静态变量。您想在每次调用 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
可以 return 互斥锁仍然锁定。
P
当它是非正数时减少它应该在它是正数时减少它。
而且 P
和 V
都执行无意义甚至有害的错误处理。如果广播失败,是否跳过解锁互斥体?是的,我们不要那样做。
让我们从一个基本的解决方案开始,一个不考虑安全或效率的解决方案。
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
在进入时解锁提供的互斥锁并在 returning 之前重新锁定它。)
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
没有意义,因为一次只能有一个线程修改信号量。通过在适当的时候让 V
和 P
调用 pthread_cond_signal
,我们可以避免白白唤醒线程。
- 我们可以忽略检查
pthread_mutex_lock
、pthread_mutex_unlock
和 pthread_cond_signal
是否在工作代码中失败,因为这些失败只是由于编码错误。
你的采购有误。
在循环内部,你释放互斥体;这使循环顶部的测试和 cond 等待都无效。
你可能想要这样的东西:
lock(&s->mutex);
while (s->val <= 0) {
wait(&s->cv, &s->mutex);
}
s->val--;
unlock(&s->mutex);
从条件变量唤醒只是表明您应该重新检查您的条件;不是断言你的病情已经到了。
我正在尝试重新创建一个 "Blackbox" 库。在我的 CS class 中,当我们应该使用信号量时(在我们的纸上决赛中),我们得到了一个 "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 调用将无法通过它,直到在同一个 sem 上调用 V
编译时出现以下错误:
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
您不需要静态变量。您想在每次调用 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
可以 return 互斥锁仍然锁定。P
当它是非正数时减少它应该在它是正数时减少它。
而且 P
和 V
都执行无意义甚至有害的错误处理。如果广播失败,是否跳过解锁互斥体?是的,我们不要那样做。
让我们从一个基本的解决方案开始,一个不考虑安全或效率的解决方案。
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
在进入时解锁提供的互斥锁并在 returning 之前重新锁定它。)
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
没有意义,因为一次只能有一个线程修改信号量。通过在适当的时候让V
和P
调用pthread_cond_signal
,我们可以避免白白唤醒线程。 - 我们可以忽略检查
pthread_mutex_lock
、pthread_mutex_unlock
和pthread_cond_signal
是否在工作代码中失败,因为这些失败只是由于编码错误。
你的采购有误。 在循环内部,你释放互斥体;这使循环顶部的测试和 cond 等待都无效。
你可能想要这样的东西:
lock(&s->mutex);
while (s->val <= 0) {
wait(&s->cv, &s->mutex);
}
s->val--;
unlock(&s->mutex);
从条件变量唤醒只是表明您应该重新检查您的条件;不是断言你的病情已经到了。