问题
Im trying to recreate a "Blackbox" library. In my CS class when we are supposed to use Semaphores (in our on paper final) we are given a "sem.h" file. there are 3 functions one for creating a new Semaphore with an inital number of tokens, one for taking a token out of a semaphore and one for placing a token into a semaphore. at 0 tokes any thread using the blocking funktion has to wait for a token.
for better understanding ive been trying to recreate this sem.h and sem.c based on some exams asking for an implementation of single funktions. since this is all intended to be done on paper, it donst compile but I feel im close
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;
}
Incase it isnt clear what this is for: The functions should limit how many threads can access a segment of code at the same time example
//global
static SEM *sem = semCreate(1);
/.../
//critical segment in threadfunction
P(sem);
doReadAndWriteGlobalList();
V(sem);
as soon as the 1st thread passes P() any subsequent calls of P wont be able to pass it until V was called on the same sem
on compiling I get the following error:
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
回答1:
You don't want a static var. You want to create a new object (memory allocation) every time semCreate
is called. As such,
static SEM *sem ={
.val=initVal
};
should be
SEM *sem = malloc(sizeof(SEM));
sem->val = initVal;
Don't forget to free the semaphore after you're done with it. That includes on error!
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;
}
Aside from that, your code has multiple problems. In short, P
is completely wrong.
P
can callpthread_cond_signal
with an unlocked mutex.P
can return with the mutex still locked.P
decrements the value when it's non-positive when it's suppose to decrement it when it's positive.
And there's the issue that both P
and V
perform pointless and even harmful error handling. Skipping unlocking the mutex if the broadcast fails? Yeah, let's not do that.
Let's start from scratch with a basic solution, one with no regards for safety or efficiency.
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;
}
Now, let's make it thread-safe through mutual exclusion.
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);
}
But that's a busy wait. Let's use a condition var to sleep until the semaphore changes. (Remember that cond_wait
unlocks the provided mutex on entry and relocks it before 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);
}
Tada!
Notes:
- There's no point in calling
pthread_cond_broadcast
since only one thread can be modify the semaphore at a time. By having bothV
andP
callpthread_cond_signal
when appropriate, we avoid waking up threads for nothing. - We can leave out checking if
pthread_mutex_lock
,pthread_mutex_unlock
andpthread_cond_signal
fail in working code as these only fail as the result of a coding error.
回答2:
Your Procure is wrong. Inside the loop, you release the mutex; which invalidates both the test at the top of the loop and the cond wait.
You probably want something like:
lock(&s->mutex);
while (s->val <= 0) {
wait(&s->cv, &s->mutex);
}
s->val--;
unlock(&s->mutex);
A wakeup from a condition variable is just an indication you should recheck your condition; not an assertion that your condition has arrived.
来源:https://stackoverflow.com/questions/60224504/how-to-create-semaphores-in-c