DISCLAIMER: this post contains edits done on the answers below, all the credits go to their respective owners.
I am trying to implement a problem which states that a resource may be used by two types of threads. There can be more threads of each type. (4 threads of type White and 6 of type Black). Any number of blacks can use the resource at the same time. Same goes for the whites one. I still cannot wrap my head around this...
I tried to implement this using mutexes. I also wanted to take into account the possible starvation this implementation might have, thus I decided to check if the number of served threads of a certain type has been reached, allowing the other type to do work. I cannot seem to be able to implement the latest.
I also wanted to take into account that whenever the other type wants to use the resource, it must await its turn, as well as the others to finish using the resource.
EDIT: I tried to use the solution of @Nominal-Animal, but it seems this deadlocks sometimes as well. Moreover, I added the missing turn to the struct. Now, I have got some extra question:
- this seems right, but doesn't work, why?
- why does need the double negation for the
isBLack
parameter insidebwlock_lock()
Now, for some code:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <wait.h>
#include <pthread.h>
#define WHITES 31
#define BLACKS 33
#define TYPES 2
#define W_ID 0
#define B_ID 1
struct bwlock
{
pthread_mutex_t lock; /* Protects the rest of the fields */
pthread_cond_t wait[2]; /* To wait for their turn */
volatile int waiting[2]; /* Number of threads waiting */
volatile int running[2]; /* Number of threads running */
volatile int started[2]; /* Number of threads started in this turn */
const int limit[2]; /* Maximum number of starts per turn */
volatile int black; /* Black threads' turn */
volatile int turn; /*The turn */
};
#define BWLOCK_INIT(whites, blacks, turn) \
{ \
PTHREAD_MUTEX_INITIALIZER, \
{PTHREAD_COND_INITIALIZER, PTHREAD_COND_INITIALIZER}, \
{0, 0}, {0, 0}, {0, 0}, {whites, blacks}, 0, turn \
}
struct bwlock resource = BWLOCK_INIT(4, 5, W_ID);
void bwlock_unlock(struct bwlock *bwl, const int isblack)
{
const int black = !!isblack; /* 0 if white, 1 if black */
pthread_mutex_lock(&(bwl->lock));
/* This thread is no longer using the resource. */
bwl->running[black]--;
/* Was this the last of this color, with others waiting? */
if (bwl->running[black] <= 0 && bwl->waiting[!black])
{
/* Yes. It's their turn. */
if (bwl->turn == black)
{
bwl->turn = !black;
/* Clear their started counter. */
bwl->started[!black] = 0;
}
/* Wake them all up. */
pthread_cond_broadcast(&(bwl->wait[!black]));
}
pthread_mutex_unlock(&(bwl->lock));
}
void bwlock_lock(struct bwlock *bwl, const int isblack)
{
const int black = !!isblack; /* 0 if white, 1 if black */
pthread_mutex_lock(&(bwl->lock));
while (1)
{
/* No runners or waiters of the other color? */
if (!(bwl->waiting[!black] < 1) && bwl->running[!black] < 1)
{
/* No; we can run. Does this change the turn? */
if (bwl->turn != black)
{
bwl->turn = black;
/* Clear started counter. */
bwl->started[black] = 0;
}
break;
}
/* Still our turn, and not too many started threads? */
if (bwl->turn == black && bwl->started[black] < bwl->limit[black])
break;
/* We must wait. */
bwl->waiting[black]++;
pthread_cond_wait(&(bwl->wait[black]), &(bwl->lock));
bwl->waiting[black]--;
}
bwl->started[black]++;
bwl->running[black]++;
pthread_mutex_unlock(&(bwl->lock));
}
typedef struct
{
int thread_id;
char *type;
int type_id;
} data;
void use_resource(int thread_id, char *type)
{
printf("U: Thread %d of type %s is using the resource!\n", thread_id, type);
}
void take_resource(int thread_id, char *type, int type_id)
{
printf("W:Thread %d of type %s is trying to get the resource!\n", thread_id, type);
bwlock_lock(&resource, type_id);
printf("W:Thread %d of type %sB got resource!\n", thread_id, type);
}
void release_resource(int thread_id, char *type, int type_id)
{
bwlock_unlock(&resource, type_id);
printf("R:Thread %d of type %s has released the resource!\n", thread_id, type);
}
void *doWork(void *arg)
{
data thread_data = *((data *)arg);
int thread_id = thread_data.thread_id;
char *type = thread_data.type;
int type_id = thread_data.type_id;
take_resource(thread_id, type, type_id);
use_resource(thread_id, type);
release_resource(thread_id, type, type_id);
return NULL;
}
data *initialize(pthread_t threads[], int size, char *type, int type_id)
{
data *args = malloc(sizeof(data) * size);
for (int i = 0; i < size; i++)
{
args[i].type = type;
args[i].thread_id = i;
args[i].type_id = type_id;
pthread_create(&threads[i], NULL, doWork, (void **)&args[i]);
}
return args;
}
void join(pthread_t threads[], int size)
{
for (int i = 0; i < size; i++)
{
pthread_join(threads[i], NULL);
}
}
int main()
{
pthread_t whites[WHITES];
pthread_t blacks[BLACKS];
char *white = "WHITE";
char *black = "BLACK";
data *w_args = initialize(whites, WHITES, white, W_ID);
data *b_args = initialize(blacks, BLACKS, black, B_ID);
join(whites, WHITES);
join(blacks, BLACKS);
free(w_args);
free(b_args);
return 0;
}
This has been compiled using gcc -g -o ba blacks_whites.c -Wall -Wextra -pthread
.