Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementation of MVar in C?

Is there any known implementation of Haskell MVar in C? There is an example on how to implement it in C++. But, I will like to implement it in C - let us say only MVar CInt equivalent in C for now. Writing synchronization primitives can be tricky. So, I will like to avoid duplication of effort if someone has already done it. I didn't understand the C++ example above well enough to confidently translate it into C - it hides the algorithmic details very well from my C++-inexperienced mind :)

The reason I am thinking about writing MVar in C is because it makes it really easy for me to use the FFI binding to an external C library to get the stream of data, and use Haskell threads to grab the data (from Storable vectors to avoid marshaling the data - MVar CInt here stores how much of the Storable vectors have been filled). I need to make sure that C threads writing to Storable locations are blocked while a Haskell thread is reading the data. That is where MVar synchronization on C side helps. It is also much faster to call unsafe or even safe C function from Haskell (~15ns for unsafe, ~150ns for safe in my test), than callback into Haskell from C (~5us). If callbacks were fast, I would have had C function call back into Haskell instead, and block on Haskell MVar.

Update:

Algorithm in pseudo-code will do as well. It should be quite easy to implement it in C, given the algorithm for newEmptyMVar, takeMVar and putMVar.

like image 769
Sal Avatar asked Jan 18 '12 04:01

Sal


1 Answers

MVar can be implemented in C using a struct like below:

typedef struct{
  pthread_cond_t put_cond;
  pthread_cond_t take_cond;
  pthread_mutex_t lock;
  void* value;
} mvar;

put_cond is used by threads that put values in MVar to signal other threads that are waiting to take value from MVar. take_cond is the analogous counterpart for take. As for scheduling, it is default scheduling.

value is a void pointer - so, the above structure can be used to protect any type of value in an MVar - of course, C will let you write that pointer outside MVar - so, it is the program's responsibility to ensure this doesn't happen (by avoiding squirreling away value pointer outside MVar - always access it through MVar functions).

Initializing MVar:

mvar* newMVar(void* arg){
 //create new mvar struct
 mvar* var=(mvar*) malloc(sizeof(mvar));
 pthread_mutex_init(&var->lock,NULL);
 pthread_cond_init(&var->take_cond,NULL);
 pthread_cond_init(&var->put_cond,NULL);
 var->value = arg;
 return (mvar*) var;
}

Empty MVar - uses above function:

mvar* newEmptyMVar(){
 return newMVar(NULL);
}

putMVar:

void putMVar(mvar* var,void* value){
  pthread_mutex_lock(&var->lock);
  while(var->value != NULL)
    pthread_cond_wait(&var->put_cond,&var->lock);//if MVar is full, wait until another thread takes the value - release the mutex,  and wait on put_cond to become true
  var->value = value;//if here, we got the signal from another thread that took MVar - MVar is empty now. OK to fill
  pthread_cond_signal(&var->take_cond);//signal other threads that value is available for taking now
  pthread_mutex_unlock(&var->lock);
}

takeMVar:

void* takeMVar(mvar* var){
  void* value;
  pthread_mutex_lock(&var->lock);
  while(var->value == NULL)
    pthread_cond_wait(&var->take_cond,&var->lock);//if MVar is empty, wait until another thread fills it - release the mutex, and   wait on take_cond to become true
  //take the value
  value = var->value;
  var->value = NULL; //push NULL value to indicate MVar is empty now
  pthread_cond_signal(&var->put_cond);//signal other threads that value is available for filling now
  pthread_mutex_unlock(&var->lock);
  return value; //return the value that was taken from MVar
}

Complete code is on github, with example which shows how to use MVar.

MVar is quite fast if there is only one thread accessing it (and heavy contention). But, under heavy contention, and multiple threads (even two), it scales very poorly. This is not a surprise because of the way pthreads work. I have found MVar in Haskell to be very good with multiple threads. This is not a surprise given how well lightweight threads and concurrency primitives are implemented in GHC.

like image 177
Sal Avatar answered Sep 27 '22 23:09

Sal