I'm trying to solve the classic "single lane tunnel" semaphore/mutex problem.
This is the code I wrote but it doesn't work and I can't understand why.
In theory the cars coming from opposite direction should cross ONLY if the tunnel is already in use by cars going in the same direction otherwise they should wait, the output should be something like:
car1_leftToRight crossing
car2_leftToRight crossing
car1_leftToRight end crossing
car2_leftToRight end crossing (ALL cars leftToRight have crossed)
car1_rightToLeft start crossing
etc..
but my current output is the one that you can see in the image I attached.
I also created a global variable (globalCarsCrossing) to keep track on how many cars are currently crossing the bridge and as you can see it seems that cars from opposite directions are crossing at the same time!
Do you have some suggestion on what I'm doing wrong?
#define UNICODE
#define _UNICODE
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <WinBase.h>
#include <process.h> //include for function _beginthreadex
int globalCarsCrossing = 0;
typedef struct {
int numTraversingCar;
int crossingTime;
int arrivalTime; //0 - arrivalTime -> random number, how much time pass between cars arrival
HANDLE mutex;
LPHANDLE bridgeMutexPtr;
int direction;
} ThParams;
DWORD WINAPI thFunction(LPVOID thParamsPtr);
void crossBridge();
int _tmain(int argc, LPTSTR argv[])
{
int timeL2R, timeR2L, timeARRIVAL, nL2R, nR2L;
LPHANDLE thL2Rarray, thR2Larray;
ThParams paramsL2R, paramsR2L;
HANDLE bridgeMutex;
if (argc < 6)
{
_ftprintf(stderr, _T("missing parameters: <timeL2R> <timeR2L> <timeARRIVAL> <nL2R> <nR2L>\n"));
exit(EXIT_FAILURE);
}
timeL2R = _ttoi(argv[1]); //WINDOWS version of "atoi"
timeR2L = _ttoi(argv[2]);
timeARRIVAL = _ttoi(argv[3]);
nL2R = _ttoi(argv[4]);
nR2L = _ttoi(argv[5]);
//allocates all threads array
thL2Rarray = (LPHANDLE)malloc(nL2R * sizeof(HANDLE));
thR2Larray = (LPHANDLE)malloc(nR2L * sizeof(HANDLE));
//initialize all parameters
bridgeMutex = CreateMutex(NULL, FALSE, NULL);
//create structs for threads
paramsL2R.mutex = CreateMutex(NULL, FALSE, NULL);
paramsL2R.bridgeMutexPtr = &bridgeMutex;
paramsL2R.arrivalTime = timeARRIVAL;
paramsL2R.numTraversingCar = 0;
paramsL2R.crossingTime = timeL2R;
paramsL2R.direction = 0;
//paramsR2L.criticalSectionPtr = &criticalSectionR2L;
paramsR2L.mutex = CreateMutex(NULL, FALSE, NULL);
paramsR2L.bridgeMutexPtr = &bridgeMutex;
paramsR2L.arrivalTime = timeARRIVAL;
paramsR2L.numTraversingCar = 0;
paramsR2L.crossingTime = timeR2L;
paramsR2L.direction = 1;
//create L2R threads
for (int i = 0; i<nL2R; i++)
thL2Rarray[i] = CreateThread(NULL, 0, thFunction, ¶msL2R, 0, NULL);
//create R2L threads
for (int i = 0; i<nR2L; i++)
thR2Larray[i] = CreateThread(NULL, 0, thFunction, ¶msR2L, 0, NULL);
//wait for ALL threads to return
WaitForMultipleObjects(nL2R, thL2Rarray, TRUE, INFINITE);
WaitForMultipleObjects(nR2L, thR2Larray, TRUE, INFINITE);
_tprintf(_T("all threads are returned\n"));
//closa all thread handle
for (int i = 0; i<nL2R; i++)
CloseHandle(thL2Rarray[i]);
for (int i = 0; i<nR2L; i++)
CloseHandle(thR2Larray[i]);
////free and release everything
free(thL2Rarray);
free(thR2Larray);
CloseHandle(bridgeMutex);
CloseHandle(paramsR2L.mutex);
CloseHandle(paramsL2R.mutex);
return 0;
}
DWORD WINAPI thFunction(LPVOID thParamsPtr)
{
ThParams *paramsPtr = (ThParams *)thParamsPtr;
WaitForSingleObject(paramsPtr->mutex, INFINITE);
paramsPtr->numTraversingCar = paramsPtr->numTraversingCar + 1;
if (paramsPtr->numTraversingCar == 1)
WaitForSingleObject(*(paramsPtr->bridgeMutexPtr), INFINITE);
globalCarsCrossing++;
_tprintf(_T("%d crossing direction: %d, TOT_cars_from_this_direction: %d, GLOBAL_CARS_CROSSING: %d\n"), GetCurrentThreadId(), paramsPtr->direction, paramsPtr->numTraversingCar, globalCarsCrossing);
ReleaseMutex(paramsPtr->mutex);
crossBridge();
WaitForSingleObject(paramsPtr->mutex, INFINITE);
paramsPtr->numTraversingCar = paramsPtr->numTraversingCar - 1;
globalCarsCrossing--;
_tprintf(_T("%d end crossing direction: %d, TOT_cars_from_this_direction: %d, GLOBAL_CARS_CROSSING %d\n"), GetCurrentThreadId(), paramsPtr->direction, paramsPtr->numTraversingCar, globalCarsCrossing);
if (paramsPtr->numTraversingCar == 0) {
_tprintf(_T("RELEASED\n"));
ReleaseMutex(*(paramsPtr->bridgeMutexPtr));
}
ReleaseMutex(paramsPtr->mutex);
return 0;
}
The problem comes from your WaitForSingleObject call:
Return code: WAIT_ABANDONED 0x00000080L
The specified object is a mutex object that was not released by the thread that owned the mutex object before the owning thread terminated. Ownership of the mutex object is granted to the calling thread and the mutex state is set to nonsignaled.
If the mutex was protecting persistent state information, you should check it for consistency.
Thread 2944 got the mutex on tunnel, made its car cross and finish, without releasing the mutex.
When thread 3560 calls WaitForSingleObject, this function returns WAIT_ABANDONED
Your code cannot do what you want because a mutex took by a thread must be released by the same thread.
A semaphore is more appropriated to lock the tunnel.
Edit:
I first post I suggested using CriticalSection, but like Mutex, a CriticalSection must be acquired and release by the same thread.
Example implementation:
#define UNICODE
#define _UNICODE
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <WinBase.h>
#include <process.h> //include for function _beginthreadex
int globalCarsCrossing = 0;
typedef struct {
int numTraversingCar;
int crossingTime;
int arrivalTime; //0 - arrivalTime -> random number, how much time pass between cars arrival
HANDLE mutex;
HANDLE bridgeSem;
int direction;
} ThParams;
DWORD WINAPI thFunction(LPVOID thParamsPtr);
void crossBridge();
int _tmain(int argc, LPTSTR argv[])
{
int timeL2R, timeR2L, timeARRIVAL, nL2R, nR2L;
LPHANDLE thL2Rarray, thR2Larray;
ThParams paramsL2R, paramsR2L;
HANDLE bridgeSem;
if (argc < 6)
{
_ftprintf(stderr, _T("missing parameters: <timeL2R> <timeR2L> <timeARRIVAL> <nL2R> <nR2L>\n"));
exit(EXIT_FAILURE);
}
timeL2R = _ttoi(argv[1]); //WINDOWS version of "atoi"
timeR2L = _ttoi(argv[2]);
timeARRIVAL = _ttoi(argv[3]);
nL2R = _ttoi(argv[4]);
nR2L = _ttoi(argv[5]);
//allocates all threads array
thL2Rarray = (LPHANDLE)malloc(nL2R * sizeof(HANDLE));
thR2Larray = (LPHANDLE)malloc(nR2L * sizeof(HANDLE));
//initialize all parameters
bridgeSem = CreateSemaphore(NULL, 1, 1, NULL);
//create structs for threads
paramsL2R.mutex = CreateMutex(NULL, FALSE, NULL);
paramsL2R.bridgeSem = bridgeSem;
paramsL2R.arrivalTime = timeARRIVAL;
paramsL2R.numTraversingCar = 0;
paramsL2R.crossingTime = timeL2R;
paramsL2R.direction = 0;
//paramsR2L.criticalSectionPtr = &criticalSectionR2L;
paramsR2L.mutex = CreateMutex(NULL, FALSE, NULL);
paramsR2L.bridgeSem = bridgeSem;
paramsR2L.arrivalTime = timeARRIVAL;
paramsR2L.numTraversingCar = 0;
paramsR2L.crossingTime = timeR2L;
paramsR2L.direction = 1;
//create L2R threads
for (int i = 0; i<nL2R; i++)
thL2Rarray[i] = CreateThread(NULL, 0, thFunction, ¶msL2R, 0, NULL);
//create R2L threads
for (int i = 0; i<nR2L; i++)
thR2Larray[i] = CreateThread(NULL, 0, thFunction, ¶msR2L, 0, NULL);
//wait for ALL threads to return
WaitForMultipleObjects(nL2R, thL2Rarray, TRUE, INFINITE);
WaitForMultipleObjects(nR2L, thR2Larray, TRUE, INFINITE);
_tprintf(_T("all threads are returned\n"));
//closa all thread handle
for (int i = 0; i<nL2R; i++)
CloseHandle(thL2Rarray[i]);
for (int i = 0; i<nR2L; i++)
CloseHandle(thR2Larray[i]);
////free and release everything
free(thL2Rarray);
free(thR2Larray);
CloseHandle(bridgeSem);
CloseHandle(paramsR2L.mutex);
CloseHandle(paramsL2R.mutex);
return 0;
}
DWORD WINAPI thFunction(LPVOID thParamsPtr)
{
ThParams *paramsPtr = (ThParams *)thParamsPtr;
WaitForSingleObject(paramsPtr->mutex, INFINITE);
paramsPtr->numTraversingCar = paramsPtr->numTraversingCar + 1;
if (paramsPtr->numTraversingCar == 1)
WaitForSingleObject(paramsPtr->bridgeSem, INFINITE);
globalCarsCrossing++;
_tprintf(_T("%d crossing direction: %d, TOT_cars_from_this_direction: %d, GLOBAL_CARS_CROSSING: %d\n"), GetCurrentThreadId(), paramsPtr->direction, paramsPtr->numTraversingCar, globalCarsCrossing);
ReleaseMutex(paramsPtr->mutex);
crossBridge();
WaitForSingleObject(paramsPtr->mutex, INFINITE);
paramsPtr->numTraversingCar = paramsPtr->numTraversingCar - 1;
globalCarsCrossing--;
_tprintf(_T("%d end crossing direction: %d, TOT_cars_from_this_direction: %d, GLOBAL_CARS_CROSSING %d\n"), GetCurrentThreadId(), paramsPtr->direction, paramsPtr->numTraversingCar, globalCarsCrossing);
if (paramsPtr->numTraversingCar == 0) {
_tprintf(_T("RELEASED\n"));
ReleaseSemaphore(paramsPtr->bridgeSem, 1, NULL);
}
ReleaseMutex(paramsPtr->mutex);
return 0;
}
Related
I tried to solve the Sleeping Barber problem using the pthread functions.
code:
#include <stdio.h>
#include <stdlib.h>
#include <semaphore.h>
#include <pthread.h>
#include <unistd.h>
#define NR_CUSTOMERS 20
#define NR_CHAIRS 3 //chairs in the waiting room + barber chair
sem_t lock;
sem_t customer;
sem_t barber;
int waitingCustomers = 0;
int barberChair = 0;
void *th_customer(void *arg)
{
int myId = (int)(size_t)arg;
int tooBusy = 0;
usleep(1000 * (rand() % 20));
printf("[C%02d] Entering the barber shop\n", myId);
sem_wait(&lock);
if (waitingCustomers < NR_CHAIRS) //if there are still empty seats, the client waits
{
++waitingCustomers;
printf("[C%02d] %d customer(s) waiting\n", myId, waitingCustomers);
}
else
{
tooBusy = 1; //if not the client leaves because it is too crowded
}
sem_post(&lock);
if (tooBusy)
{
printf("[C%02d] Too busy, will come back another day.\n", myId);
}
else //one of the waiting customers is served
{
sem_wait(&customer);
sem_wait(&lock);
--waitingCustomers;
sem_post(&lock);
barberChair = myId;
sem_post(&barber);
printf("[C%02d] being served\n", myId);
usleep(1000);
}
return NULL;
}
void *th_barber()
{
for (;;)
{
sem_post(&customer);
sem_wait(&barber);
printf("[B ] Serving customer %d\n", barberChair);
usleep(1000);
}
return NULL;
}
int main()
{
int i;
pthread_t tidC[NR_CUSTOMERS], tidB;
srand(time(NULL));
sem_init(&lock, 0, 1);
sem_init(&customer, 0, 0);
sem_init(&barber, 0, 0);
pthread_create(&tidB, NULL, th_barber, NULL);
for (i = 0; i < NR_CUSTOMERS; i++)
{
pthread_create(&tidC[i], NULL, th_customer, (void *)(size_t)(i + 1));
}
for (i = 0; i < NR_CUSTOMERS; i++)
{
pthread_join(tidC[i], NULL);
}
pthread_cancel(tidB);
pthread_join(tidB, NULL);
sem_destroy(&lock);
sem_destroy(&customer);
sem_destroy(&barber);
return 0;
}
The problem: I'm worried about possible concurrence, not the data, that's correct, but in terms of not using available resources: a customer may not receive a haircut due to the maximum number of customers, but one is already on his way to the barber chair, but does he who enters just not notice that his place has just been vacated?
What other problems can be hidden?
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <wait.h>
#include <pthread.h>
int item_to_produce, curr_buf_size;
int total_items, max_buf_size, num_workers, num_masters;
int consumed_items;
int *buffer;
pthread_mutex_t mutex;
pthread_cond_t has_data;
pthread_cond_t has_space;
void print_produced(int num, int master) {
printf("Produced %d by master %d\n", num, master);
}
void print_consumed(int num, int worker) {
printf("Consumed %d by worker %d\n", num, worker);
}
//consume items in buffer
void *consume_requests_loop(void *data)
{
int thread_id = *((int *)data);
while(1)
{
pthread_mutex_lock(&mutex); // mutex lock for consume
if(consumed_items == total_items) {
pthread_mutex_unlock(&mutex);
break;
}
while(curr_buf_size == 0) {
pthread_cond_wait(&has_data, &mutex);
}
print_consumed(buffer[(curr_buf_size--)-1], thread_id);
consumed_items++;
pthread_cond_signal(&has_space);
pthread_mutex_unlock(&mutex);
}
return 0;
}
//produce items and place in buffer
//modify code below to synchronize correctly
void *generate_requests_loop(void *data)
{
int thread_id = *((int *)data);
while(1) {
pthread_mutex_lock(&mutex); // mutex lock for consume
//all of items are produced
//master threads need to join
if(item_to_produce == total_items) {
pthread_mutex_unlock(&mutex);
break;
}
//there is no item to read
while (curr_buf_size == max_buf_size) {
pthread_cond_wait(&has_space, &mutex);
}
buffer[curr_buf_size++] = item_to_produce;
print_produced(item_to_produce, thread_id);
item_to_produce++;
pthread_cond_signal(&has_data);
pthread_mutex_unlock(&mutex); // mutex_produce unlock
}
return 0;
}
//write function to be run by worker threads
//ensure that the workers call the function print_consumed when they consume an item
int main(int argc, char *argv[])
{
int *master_thread_id; // array of master_thread_id
int *worker_thread_id; // array of worker_thread_id
pthread_t *master_thread; // array of master_thread
pthread_t *worker_thread; // array of worker_thread
item_to_produce = 0; // item will be produced by master_thread at next time
curr_buf_size = 0; // index of item will be saved in
consumed_items = 0;
int i;
if (argc < 5) {
printf("./master-worker #total_items #max_buf_size #num_workers #masters e.g. ./exe 10000 1000 4 3\n");
exit(1);
}
else {
num_masters = atoi(argv[4]);
num_workers = atoi(argv[3]);
total_items = atoi(argv[1]);
max_buf_size = atoi(argv[2]);
}
buffer = (int *)malloc (sizeof(int) * max_buf_size);
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&has_space, NULL);
pthread_cond_init(&has_data, NULL);
//create master producer threads
master_thread_id = (int *)malloc(sizeof(int) * num_masters);
master_thread = (pthread_t *)malloc(sizeof(pthread_t) * num_masters);
for (i = 0; i < num_masters; i++)
master_thread_id[i] = i;
for (i = 0; i < num_masters; i++)
pthread_create(&master_thread[i], NULL, generate_requests_loop, (void *)&master_thread_id[i]);
//create worker consumer threads
worker_thread_id = (int *)malloc(sizeof(int) * num_workers);
worker_thread = (pthread_t *)malloc(sizeof(pthread_t) * num_workers);
for (i = 0; i < num_workers; i++)
worker_thread_id[i] = i;
for (i = 0 ; i < num_workers; i++)
pthread_create(&worker_thread[i], NULL, consume_requests_loop, (void *)&worker_thread_id[i]);
//wait for all threads to complete
for (i = 0; i < num_masters; i++)
{
pthread_join(master_thread[i], NULL);
printf("master %d joined\n", i);
}
for (i = 0; i < num_workers; i++)
{
pthread_join(worker_thread[i], NULL);
printf("worker %d joined\n", i);
}
/*----Deallocating Buffers---------------------*/
free(buffer);
free(master_thread_id);
free(master_thread);
free(worker_thread_id);
free(worker_thread);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&has_data);
pthread_cond_destroy(&has_space);
return 0;
}
This code produces a number in the range of given numbers through the argument and consumes it.
But producer produces a number outside the range and doesn't join if it matches the condition. The consumer is too.
e.g when I give range of number like 0~39(total_item = 500), buff size 30(max_buf_size), num_workers 5, num_master 3, it doesn't produce and consume number only 0~39.
It produces and consumes numbers over 40.
In that way the thread is in a loop. To put the thread in sleep you can use, for example, the condition variables. (You can read this for more info https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_wait.html)
This is my first time dealing with threads.
When I run the program without the GetCurrentThreadId() function it executes without any issue.
When I add that line of code it still executes but crashes once it reaches the end. Why is this?
#include <Windows.h>
#include <stdio.h>
#include <conio.h>
static int tix[500];
static int done = 0;
HANDLE ghSemaphore;
DWORD WINAPI ThreadFunction();
int main(void)
{
DWORD threadID1, threadID2, threadID3, threadID4;
HANDLE hThread1, hThread2, hThread3, hThread4;
for (int i = 0; i < 500; i++) //initialize array
{
tix[i] = 0;
}
ghSemaphore = CreateSemaphore(NULL, 1, 10, NULL);
hThread1 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, &threadID1);
hThread2 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, &threadID2);
hThread3 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, &threadID3);
hThread4 = CreateThread(NULL, 0, ThreadFunction, NULL, 0, &threadID4);
//printf("The thread ID: %d.\n", threadID1);
//printf("The thread ID: %d.\n", threadID2);
//printf("The thread ID: %d.\n", threadID3);
//printf("The thread ID: %d.\n", threadID4);
if (done = 1)
{
CloseHandle(hThread1);
CloseHandle(hThread2);
CloseHandle(hThread3);
CloseHandle(hThread4);
}
for (int j = 0; j < 500; j++)
{
if (tix[j] = 0)
{
printf("not sold");
}
else if (tix[j] = 1)
{
printf("sold");
}
}
return 0;
}
DWORD WINAPI ThreadFunction()
{
WaitForSingleObject(ghSemaphore, 0);
printf("current thread running : %d\n", GetCurrentThreadId());
int i = 0;
if (done != 0) // if loop to test wether or not the array is full
{
while (tix[i] = 1) //traverse the array to find a open spot
{
i++;
}
tix[i] = 1;
}
if (i == 499) //if i is 499, set test variable to 1
{
done = 1;
return 0;
}
ReleaseSemaphore(ghSemaphore, 1, NULL);
}
Your thread function has incorrect signature. Thread takes one PVOID context argument:
DWORD WINAPI ThreadProc(
_In_ LPVOID lpParameter
);
Your threads could exit without releasing a semaphore. Also since you had initialized it with a value, that is greater than thread amount and never check WaitForSingleObject result, no synchronization is provided and multiple threads will modify a shared buffers in inconsistent manner. Even worse - nothing stops your program main thread exiting earlier, than ThreadFunction.
There is no return statement in the end of your thread function, so this is an undefined behavior. In fact it is a great wonder that your code even compiles. This entire approach to multithreading is incorrect and has to be remade from the scratch.
I am trying to implement a code to practice synchronization, so might not be best design or approach but goal is as below
Main thread
Creates a payload of 100 integers and waits for any thread to be available
When it gets signal from a thread its available - it unlocks the payload for copying and proceeds to create another payload
Worker thread
on creation of it makes itself available for data processing and sends signal that its available
Tries to lock the data payload from main thread and copy it to local array
( observing bug here - not able to access data properly)
Turn off the sign of available
( unable to turn off available state to off)
Keep processing data through local copy
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdbool.h>
#define WORKERS 2
#define ARRAY_ELEMENTS 100
#define MAX 1000
pthread_mutex_t mutex_bucket1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex_signal = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond_go = PTHREAD_COND_INITIALIZER;
pthread_cond_t cond_busy = PTHREAD_COND_INITIALIZER;
static int value = 0;
bool available = false;
void *worker_thread(void *pbucket)
{
sleep(5);
while(1)
{
unsigned int count = 0;
int local_array[ARRAY_ELEMENTS];
int *ptbucket = (int*)pbucket;
setbuf(stdout, NULL);
pthread_mutex_lock(&mutex_signal);
printf(" -------------- \n chainging state to available \n --------- ");
available = true;
printf(" -------------- \n from thread sending go signal \n --------- ");
pthread_cond_signal(&cond_go);
pthread_mutex_unlock(&mutex_signal);
pthread_mutex_lock(&mutex_bucket1);
printf(" -------------- \n data part locked in thread for copying \n --------- ");
while(count < ARRAY_ELEMENTS)
{
printf(" %d - \n", ptbucket[count]); /***incorrect values***/
local_array[count] = ptbucket[count];
count++;
}
pthread_mutex_unlock(&mutex_bucket1);
/*Never able to acquire mutex_signal and change state to not available*/ **BUG**
pthread_mutex_lock(&mutex_signal);
printf(" -------------- \n chainging state to not available \n --------- ");
available = false;
pthread_mutex_unlock(&mutex_signal);
count = 0;
while(count < ARRAY_ELEMENTS)
{
printf(" %d - \n", local_array[count]);
count++;
}
printf(" -------------- \n about to sleep for 5secs \n --------- ");
sleep(5);
}
}
int main(void)
{
pthread_t thread_id[WORKERS];
unsigned int* pbucket1 = (int*) malloc(sizeof(int) * ARRAY_ELEMENTS);
unsigned int* pbucket;
for(int i = 0; i < WORKERS - 1; i++)
{
pthread_create(&thread_id[i], NULL, worker_thread, (void *) pbucket);
}
for(int i = 0; i < MAX; i++)
{
unsigned int count = 0;
pbucket = pbucket1;
// Make the payload ready
pthread_mutex_lock(&mutex_bucket1);
printf(" -------------- creating data payload --------- \n");
while(count < ARRAY_ELEMENTS)
{
pbucket1[count] = i;
i++;
count++;
}
printf(" -------------- \n waiting for go signal \n --------- ");
while(!available)
{
pthread_cond_wait(&cond_go, &mutex_signal);
}
pthread_mutex_unlock(&mutex_bucket1);
/*I believe after we unlock variable "available" can be mutexed
again by other thread but seems thinking is flawed */
printf(" -------------- \n Main thread sleep for 3 seconds \n --------- ");
sleep(3);
}
for(int i = 0; i < WORKERS; i++)
{
pthread_join(thread_id[i], NULL);
}
return 0;
}
I think some of your idea is backwards; It shouldn't be the main context that is waiting, it should be the worker threads waiting for data ...
The job of the main thread should be to keep populating the payload and waking one thread at a time to process it.
So here's some scribbled code that is a little more sensible, I think:
/**
file: answer.c
compile: gcc -o answer answer.c -pthread
usage: answer [numThreads] [numElements]
**/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#define STATE_WAIT 1
#define STATE_READY 2
void *routine(void*);
typedef struct _shared_t {
pthread_mutex_t m;
pthread_cond_t c;
unsigned char state;
int *payload;
size_t numElements;
pthread_t *threads;
size_t numThreads;
} shared_t;
static inline void shared_init(shared_t *shared, size_t numThreads, size_t numElements) {
memset(shared, 0, sizeof(shared_t));
pthread_mutex_init(&shared->m, NULL);
pthread_cond_init(&shared->c, NULL);
shared->state = STATE_WAIT;
shared->numThreads = numThreads;
shared->numElements = numElements;
{
int it = 0;
shared->threads = (pthread_t*) calloc(shared->numThreads, sizeof(pthread_t));
while (it < shared->numThreads) {
if (pthread_create(&shared->threads[it], NULL, routine, shared) != 0) {
break;
}
it++;
}
}
}
static inline void shared_populate(shared_t *shared) {
if (pthread_mutex_lock(&shared->m) != 0) {
return;
}
shared->payload = (int*) calloc(shared->numElements, sizeof(int));
{
int it = 0,
end = shared->numElements;
while (it < end) {
shared->payload[it] = rand();
it++;
}
}
shared->state = STATE_READY;
pthread_cond_signal(&shared->c);
pthread_mutex_unlock(&shared->m);
}
static inline void shared_cleanup(shared_t *shared) {
int it = 0,
end = shared->numThreads;
while (it < end) {
pthread_join(shared->threads[it], NULL);
}
pthread_mutex_destroy(&shared->m);
pthread_cond_destroy(&shared->c);
free(shared->threads);
}
void* routine(void *arg) {
shared_t *shared = (shared_t*) arg;
int *payload;
do {
if (pthread_mutex_lock(&shared->m) != 0) {
break;
}
while (shared->state == STATE_WAIT) {
pthread_cond_wait(&shared->c, &shared->m);
}
payload = shared->payload;
shared->state = STATE_WAIT;
pthread_mutex_unlock(&shared->m);
if (payload) {
int it = 0,
end = shared->numElements;
while (it < end) {
printf("Thread #%ld got payload %p(%d)=%d\n",
pthread_self(), payload, it, payload[it]);
it++;
}
free(payload);
}
} while(1);
pthread_exit(NULL);
}
int main(int argc, char *argv[]) {
shared_t shared;
int numThreads = argc > 1 ? atoi(argv[1]) : 1;
int numElements = argc > 2 ? atoi(argv[2]) : 100;
shared_init(&shared, numThreads, numElements);
do {
shared_populate(&shared);
} while (1);
shared_cleanup(&shared);
return 0;
}
Obviously, the code above is not very tolerant of errors, and is not easy to shutdown cleanly ... it's illustration only.
Let's first look at main so that we know what the flow of the main program is going to be:
int main(int argc, char *argv[]) {
shared_t shared;
int numThreads = argc > 1 ? atoi(argv[1]) : 1;
int numElements = argc > 2 ? atoi(argv[2]) : 100;
shared_init(&shared, numThreads, numElements);
do {
shared_populate(&shared);
} while (1);
shared_cleanup(&shared);
return 0;
}
It keeps a shared_t on the stack:
typedef struct _shared_t {
pthread_mutex_t m;
pthread_cond_t c;
unsigned char state;
int *payload;
size_t numElements;
pthread_t *threads;
size_t numThreads;
} shared_t;
Mostly self explanatory, mutex, condition and state are required for synchronization.
First of all the shared_t must be initialized with mutex, condition, state and threads using the provided options:
static inline void shared_init(shared_t *shared, size_t numThreads, size_t numElements) {
memset(shared, 0, sizeof(shared_t));
pthread_mutex_init(&shared->m, NULL);
pthread_cond_init(&shared->c, NULL);
shared->state = STATE_WAIT;
shared->numThreads = numThreads;
shared->numElements = numElements;
{
int it = 0;
shared->threads = (pthread_t*) calloc(shared->numThreads, sizeof(pthread_t));
while (it < shared->numThreads) {
if (pthread_create(&shared->threads[it], NULL, routine, shared) != 0) {
break;
}
it++;
}
}
}
When the worker threads are created by this routine, they are forced into a waiting state.
The first call to shared_populate in the loop awakens the first thread after setting the payload to some random numbers:
static inline void shared_populate(shared_t *shared) {
if (pthread_mutex_lock(&shared->m) != 0) {
return;
}
shared->payload = (int*) calloc(shared->numElements, sizeof(int));
{
int it = 0,
end = shared->numElements;
while (it < end) {
shared->payload[it] = rand();
it++;
}
}
shared->state = STATE_READY;
pthread_cond_signal(&shared->c);
pthread_mutex_unlock(&shared->m);
}
Note the use of pthread_cond_signal over pthread_cond_broadcast, because we only want to wake the first thread.
void* routine(void *arg) {
shared_t *shared = (shared_t*) arg;
int *payload;
do {
if (pthread_mutex_lock(&shared->m) != 0) {
break;
}
while (shared->state == STATE_WAIT) {
pthread_cond_wait(&shared->c, &shared->m);
}
payload = shared->payload;
shared->state = STATE_WAIT;
pthread_mutex_unlock(&shared->m);
if (payload) {
int it = 0,
end = shared->numElements;
while (it < end) {
printf("Thread #%ld got payload %p(%d)=%d\n",
pthread_self(), payload, it, payload[it]);
it++;
}
free(payload);
}
} while(1);
pthread_exit(NULL);
}
So we wake up in routine at the call to pthread_cond_wait, the state has changed, so we break out of the loop, we save the pointer to the payload, reset the state to WAIT, and release the mutex.
At this point main can repopulate the payload and awaken the next thread, meanwhile the current worker thread can process, and then free the payload.
Some advice:
Always use as few mutex and condition variables as possible (KISS)
Research the atomic nature of condition variables
Always follow the basic rules regarding acquisition and release of mutex and signaling of condition variables:
If you locked it, unlock it.
Only ever wait for something: predicated wait loops are absolutely required, all the time.
If you can't reproduce what I done, then take the code and try to expand upon it; The first thing you need to do is be able to shutdown the process gracefully (enter shared_cleanup), maybe you need a variable sized payload, or some other requirement not mentioned in the original question.
Note about printf ... appending to a stream is not guaranteed to be atomic, it so happens that most of the time on *nix it is ... since we are just doing show and tell, we don't need to care about that ... ordinarily, do not rely on atomicity for any stream operations ...
Basically what I am trying to do is simulate multithreading on a single thread with context switching. I set up an alarm for every 10 microseconds, and I switch the context from one to another thread. The problem is that about one in 5 runs ends up with a seg fault right after the alarm finishes the swapcontext, at least that is where I traced it with gdb.
Here are my source files
main.c
#include "umt.h"
void f()
{
int x = 10;
printf("starting thread\n");
while(x)
{
printf("thread %d\n", x);
sleep(1);
x--;
}
}
int main()
{
int x = 0, y, z;
umt_init();
y = umt_thread_create(f);
printf("starting main\n");
if(y == 0)
{
printf("Problems with creating thread\n");
return;
}
x = 10;
z = 1;
while(x)
{
printf("main\n");
x--;
}
umt_thread_join(y);
printf("done waiting\n");
return 0;
}
UMT.h
#include <sys/time.h>
#include <stdio.h>
#include <signal.h>
#include <ucontext.h>
#include <stdlib.h>
#define TRUE 1
#define FALSE 0
typedef struct _umt_thread
{
int thread_id;
ucontext_t context;
void (*handler)(void);
int hasFinished;
}umt_thread, *pumt_thread;
void umt_init();
int umt_thread_create(void (*handler)(void));
void umt_thread_join(int thr);
and umt.c
#include "umt.h"
#define MAIN_CONTEXT 0
#define STACK_SIZE 1638400
int currentThread;
char threadpool[15];
pumt_thread threads;
void signal_thread_finish();
void thread_handler()
{
threads[currentThread].handler();
signal_thread_finish();
}
void thread_scheduler();
void signal_thread_finish()
{
threads[currentThread].hasFinished = TRUE;
threadpool[currentThread] = 0;
thread_scheduler();
}
void thread_scheduler()
{
int nextThread = 0, curThread = 0;
int x = 0;
ucontext_t *con1, *con2;
nextThread = currentThread + 1;
while(1)
{
if(nextThread == 15)
nextThread = 0;
if(nextThread == currentThread)
break;
if(threadpool[nextThread] == 1)
break;
nextThread++;
}
if(nextThread == currentThread)
return;
curThread = currentThread;
currentThread = nextThread;
con1 = &(threads[curThread].context);
con2 = &(threads[nextThread].context);
x = swapcontext(con1, con2);
}
void umt_init()
{
ucontext_t context;
struct itimerval mytimer;
int i;
stack_t new_stack;
getcontext(&context);
threads = (pumt_thread)malloc(sizeof(umt_thread) * 15);
threads[MAIN_CONTEXT].thread_id = MAIN_CONTEXT;
threads[MAIN_CONTEXT].context = context;
threadpool[MAIN_CONTEXT] = 1;
for(i = 1;i<15;i++)
{
threadpool[i] = 0;
}
currentThread = 0;
new_stack.ss_sp = (char*)malloc(STACK_SIZE);
new_stack.ss_size = STACK_SIZE;
new_stack.ss_flags = 0;
i = sigaltstack(&new_stack, NULL);
if(i != 0)
{
printf("problems assigning new stack for signaling\n");
}
signal(SIGALRM, thread_scheduler);
mytimer.it_interval.tv_sec = 0;
mytimer.it_interval.tv_usec = 10;
mytimer.it_value.tv_sec = 0;
mytimer.it_value.tv_usec = 5;
setitimer(ITIMER_REAL, &mytimer, 0);
}
int umt_thread_create(void (*handler)(void))
{
ucontext_t context;
int i, pos;
for(i = 1;i<15;i++)
{
if(threadpool[i] == 0)
{
pos = i;
break;
}
}
if(i == 15)
{
printf("No empty space in the threadpool\n");
return -1;
}
if(getcontext(&context) == -1)
{
printf("Problems getting context\n");
return 0;
}
context.uc_link = 0;//&(threads[MAIN_CONTEXT].context);
context.uc_stack.ss_sp = (char*)malloc(STACK_SIZE);
if(context.uc_stack.ss_sp == NULL)
{
printf("Problems with allocating stack\n");
}
context.uc_stack.ss_size = STACK_SIZE;
context.uc_stack.ss_flags = 0;
makecontext(&context, thread_handler, 0);
threads[pos].thread_id = pos;
threads[pos].context = context;
threads[pos].handler = handler;
threads[pos].hasFinished = FALSE;
threadpool[pos] = 1;
printf("Created thread on pos %d\n", pos);
return pos;
}
void umt_thread_join(int tid)
{
while(!threads[tid].hasFinished)
{
}
}
I tried a lot of combinations and tried tracing by instruction but could not arrive to a conclusion or idea as to what might cause this seg fault. Thanks
Few issues I see (some are related to segfault + some other comments)
You scheduler (thread_scheduler) should be in a critical section, e.g. you should block any alarm signals (or ignore them) so that the handing of the threadpool is done in a way that doesn't corrupt it. you can either use sigprocmask or a volatile boolean variable that will silence the alarm (note this is not the same as the user threads mutex, just an internal synchronization to your scheduling logic)
your clock ticks way too fast IMHO, this is in micro seconds, not milliseconds, so 1000 microseconds for tv_usec might make more sense for testing purposes.
small stack sizes might also cause a seg fault but it seems your stack is big enough.
p.s. there is a better way to handle join, you currently waste lot's of CPU cycles on it, why not simply avoid switching to a thread that called join, untill the thread that it's waiting for has terminated?