W7 - Inter-Process Communication (IPC)

Class: CSCE-410-02159


Notes:

Interprocess Communication

Ways to Communicate

Notes:

Communication through Files

1-account.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>


#define AMOUNT 10000

void withdraw(int amount){

  int balance;
  FILE *f = fopen("bank_account.txt","r"); // Open Bank Account
  fscanf(f, "%d\n", &balance);             // Read Balance
  fclose(f);                               // Close Bank Account

  balance = balance - amount;              // Withdraw Amount

  f = fopen("bank_account.txt","w");       // Open Bank Account
  fprintf(f, "%d\n", balance);             // Write Balance
  fclose(f);                               // Close Account
  exit(0);
}

void deposit(int amount){

  int balance;
  FILE *f = fopen("bank_account.txt","r"); // Open Bank Account
  fscanf(f, "%d\n", &balance);             // Read Balance
  fclose(f);                               // Close Bank Account

  balance = balance + amount;              // Deposit Amount

  f = fopen("bank_account.txt","w");       // Open Bank Account
  fprintf(f, "%d\n", balance);             // Write Balance
  fclose(f);                               // Close Account
  exit(0);
}

int main(void){
  int balance;
  pid_t pid1, pid2;
  FILE *f;

  pid1 = fork(); // fork a child process
  if (pid1 == 0) {
    deposit(AMOUNT); // Child deposits amount
  }

  pid2 = fork(); // fork a child process
  if (pid2 == 0) {
    withdraw(AMOUNT); // Child withdraws amount
  }

  waitpid(-1,NULL,0);
  waitpid(-1,NULL,0);

  f = fopen("bank_account.txt","r");
  fscanf(f, "%d\n", &balance);
  fclose(f);

  printf("Balance: %d\n", balance);

  return 0;
}

Notes:

The same problem can also apply with threads: 2-threads_plain.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#define AMOUNT 1
#define REPEAT 100000

int balance = 0;
pthread_t thread_id[2];

void* withdraw(void *input) {
  int i;
  for (i=0;i<REPEAT;i++)
    balance = balance - AMOUNT;
  pthread_exit(NULL); // Thread's job is done
}

void* deposit(void *input) {
  int i;
  for (i=0;i<REPEAT;i++)
    balance = balance + AMOUNT;
  pthread_exit(NULL); // Thread's job is done
}

int main(void) {
  pthread_create(&thread_id[0], NULL, withdraw, NULL); // create a thread
  pthread_create(&thread_id[1], NULL, deposit, NULL); // create a thread

  pthread_join(thread_id[0], NULL);
  pthread_join(thread_id[1], NULL);

  printf("Balance: %d \n", balance);
  return 0;
}

Notes:

Murphy's Law

Race Condition

Notes:

How do we avoid Race Conditions?

Notes:

Mutual Exclusion using Critical Regions

image-13.png459x234

Notes:

Lock Variables

while(1) {
	if (lock == 0){
		lock = 1;
		critical_region();
		lock = 0;
		break;
	}
}

Notes:

3-threads_lock.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#define AMOUNT 1
#define REPEAT 100000

#define UNLOCKED 0
#define LOCKED   1
int lock = UNLOCKED;

int balance = 0;
pthread_t thread_id[2];

void* withdraw(void *input) {
  int i;
  for (i=0;i<REPEAT;i++){
    while(lock == LOCKED);       // Block until is unlocked...
    lock = LOCKED;               // Lock
    balance = balance - AMOUNT;  // Access critical region
    lock = UNLOCKED;             // Unlock
  }
  pthread_exit(NULL); // Thread's job is done
}

void* deposit(void *input) {
  int i;
  for (i=0;i<REPEAT;i++){
    while(lock == LOCKED);       // Block until is unlocked...
    lock = LOCKED;               // Lock
    balance = balance + AMOUNT;  // Access critical region
    lock = UNLOCKED;             // Unlock
  }
  pthread_exit(NULL); // Thread's job is done
}

int main(void) {
  pthread_create(&thread_id[0], NULL, withdraw, NULL); // create a thread
  pthread_create(&thread_id[1], NULL, deposit, NULL); // create a thread

  pthread_join(thread_id[0], NULL);
  pthread_join(thread_id[1], NULL);

  printf("Balance: %d \n", balance);
  return 0;
}

Strict Alternation

while (TRUE) {
    while (turn != 0) /* loop */ ;
    critical_region();
    turn = 1;
    noncritical_region();
}
while (TRUE) {
    while (turn != 1) /* loop */ ;
    critical_region();
    turn = 0;
    noncritical_region();
}

Notes:

Hardware-based Solutions

Notes:

Disabling Interrupts

while(1) {
	disable_interrupts();
	critical_region();
	enable_interrupts();
}

Notes:

Atomic Instructions

TSL REGISTER,LOCK copy lock to register and set lock to 1
CMP REGISTER,#0 was lock zero?
JNE enter_region if it was not zero, lock was set, so loop
RET return to caller; critical region entered
enter_region:
	TSL REGISTER,LOCK    | copy lock to register and set lock to 1
	CMP REGISTER,#0      | was lock zero?
	JNE enter_region     | if it was not zero, lock was set, so loop
	RET                  | return to caller; critical region entered

leave_region:
	MOVE LOCK,#0         | store a 0 in lock
	RET                  | return to caller

Spinlock and Busy Waiting

Mutex

TSL REGISTER,MUTEX copy mutex to register and set mutex to 1
CMP REGISTER,#0 was mutex zero?
JZE ok if it was zero, mutex was unlocked, so return
CALL thread_yield | mutex is busy; schedule another thread
JMP mutex_lock try again
RET | return to caller; critical region entered
mutex_lock:
	TSL REGISTER,MUTEX    | copy mutex to register and set mutex to 1
	CMP REGISTER,#0       | was mutex zero?
	JZE ok                | if it was zero, mutex was unlocked, so return
	CALL thread_yield     | mutex is busy; schedule another thread
	JMP mutex_lock        | try again
	RET                   | return to caller; critical region entered

mutex_unlock:
	MOVE MUTEX,#0         | store 0 in a mutex
	RET                   | return to caller

Avoiding Race Conditions with Mutexes

while(1) {
	mutex_lock();
	critical_region();
	mutex_unlock();
}

Mutexes in Pthreads

Thread call Description
Pthread_mutex_init Create a mutex
Pthread_mutex_destroy Destroy an existing mutex
Pthread_mutex_lock Acquire a lock or block
Pthread_mutex_trylock Acquire a lock or fail
Pthread_mutex_unlock Release a lock

4-threads_mutex.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

#define AMOUNT 1
#define REPEAT 100000

pthread_mutex_t lock;

int balance = 0;
pthread_t thread_id[2];

void* withdraw(void *input) {
  int i;
  for (i=0;i<REPEAT;i++){
    pthread_mutex_lock(&lock);   // Lock
    balance = balance - AMOUNT;  // Access critical region
    pthread_mutex_unlock(&lock); // Unlock
  }
  pthread_exit(NULL); // Thread's job is done
}

void* deposit(void *input) {
  int i;
  for (i=0;i<REPEAT;i++){
    pthread_mutex_lock(&lock);   // Lock
    balance = balance + AMOUNT;  // Access critical region
    pthread_mutex_unlock(&lock); // Unlock
  }
  pthread_exit(NULL); // Thread's job is done
}

int main(void) {
  pthread_mutex_init(&lock, NULL); // init mutex

  pthread_create(&thread_id[0], NULL, withdraw, NULL); // create a thread
  pthread_create(&thread_id[1], NULL, deposit, NULL); // create a thread

  pthread_join(thread_id[0], NULL);
  pthread_join(thread_id[1], NULL);

  printf("Balance: %d \n", balance);
  return 0;
}

Notes:

File Locks

while(1) {
	lockf(LOCK);
	critical_region();
	lockf(UNLOCK);
}

Notes:

5-account_filelock.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

#define AMOUNT 10000

FILE *lock;

void withdraw(int amount){

  int balance;
  lockf(fileno(lock), F_LOCK, 0);          // Lock
  FILE *f = fopen("bank_account.txt","r"); // Open Bank Account
  fscanf(f, "%d\n", &balance);             // Read Balance
  fclose(f);                               // Close Bank Account

  balance = balance - amount;              // Withdraw Amount

  f = fopen("bank_account.txt","w");       // Open Bank Account
  fprintf(f, "%d\n", balance);             // Write Balance
  fclose(f);                               // Close Account
  lockf(fileno(lock), F_ULOCK, 0);         // Unlock
  exit(0);
}

void deposit(int amount){

  int balance;
  lockf(fileno(lock), F_LOCK, 0);          // Lock
  FILE *f = fopen("bank_account.txt","r"); // Open Bank Account
  fscanf(f, "%d\n", &balance);             // Read Balance
  fclose(f);                               // Close Bank Account

  balance = balance + amount;              // Deposit Amount

  f = fopen("bank_account.txt","w");       // Open Bank Account
  fprintf(f, "%d\n", balance);             // Write Balance
  fclose(f);                               // Close Account
  lockf(fileno(lock), F_ULOCK, 0);         // Unlock
  exit(0);
}

int main(void){
  int balance;
  pid_t pid1, pid2;
  FILE *f;

  lock = fopen("lock","w+");

  pid1 = fork(); // fork a child process
  if (pid1 == 0) {
    deposit(AMOUNT); // Child deposits amount
  }

  pid2 = fork(); // fork a child process
  if (pid2 == 0) {
    withdraw(AMOUNT); // Child withdraws amount
  }

  waitpid(-1,NULL,0);
  waitpid(-1,NULL,0);

  f = fopen("bank_account.txt","r");
  fscanf(f, "%d\n", &balance);
  fclose(f);

  printf("Balance: %d\n", balance);

  return 0;
}

Notes:

Back to the Bank Account Example

send_money(int a, int b, int amount) {
	mutex_lock(&mutex[a]);
	mutex_lock(&mutex[b]);
	account[a] = account[a] - amount;
	account[b] = account[b] + amount;
	mutex_unlock(&mutex[b]);
	mutex_unlock(&mutex[a]);
}

The Producer-Consumer Problem

Notes:

#define N 100  /* number of slots in the buffer */
int count = 0; /* number of items in the buffer */

void producer(void)
{
    int item;
    while (TRUE) {               /* repeat forever */
        item = produce_item();   /* generate next item */
        if (count == N) sleep(); /* if buffer is full, go to sleep */
        insert_item(item);       /* put item in buffer */
		count = count + 1;       /* increment count of items in buffer */
        if (count == 1) wakeup(consumer); /* was buffer empty? */
    }
}

void consumer(void)
{
    int item;
    while (TRUE) {               /* repeat forever */
        if (count == 0) sleep(); /* if buffer is empty, got to sleep */
        item = remove_item();    /* take item out of buffer */
        count = count - 1;       /* decrement count of items in buffer */
        if (count == N - 1) wakeup(producer); /* was buffer full? */
        consume_item(item);      /* print item */
    }
}

Two Race Conditions

Notes:

Semaphores

Solving The Producer-Consumer Problem (semaphores)

#define N 100 /* number of slots in the buffer */
typedef int semaphore; /* semaphores are a special kind of int */
semaphore mutex = 1; /* controls access to critical region */
semaphore empty = N; /* counts empty buffer slots */
semaphore full = 0; /* counts full buffer slots */

void producer(void)
{
    int item;
    while (TRUE) { /* TRUE is the constant 1 */
        item = produce_item(); /* generate something to put in buffer */
        down(&empty); /* decrement empty count */
        down(&mutex); /* enter critical region */
        insert_item(item); /* put new item in buffer */
        up(&mutex); /* leave critical region */
        up(&full); /* increment count of full slots */
    }
}

void consumer(void)
{
    int item;
    while (TRUE) { /* infinite loop */
        down(&full); /* decrement full count */
        down(&mutex); /* enter critical region */
        item = remove_item(); /* take item from buffer */
        up(&mutex); /* leave critical region */
        up(&empty); /* increment count of empty slots */
        consume_item(item); /* do something with the item */
    }
}

Notes:

Semaphore and Mutexes

Notes:

Message Passing

Notes:

Solving The Producer-Consumer Problem (messages)

#define N 100 /* number of slots in the buffer */

void producer(void)
{
    int item;
    message m; /* message buffer */
    while (TRUE) {
        item = produce_item(); /* generate something to put in buffer */
        receive(consumer, &m); /* wait for an empty to arrive */
        build_message(&m, item); /* construct a message to send */
        send(consumer, &m); /* send item to consumer */
    }
}

void consumer(void)
{
    int item, i;
    message m;
    for (i = 0; i < N; i++) send(producer, &m); /* send N empties */
    while (TRUE) {
        receive(producer, &m); /* get message containing item */
        item = extract_item(&m); /* extract item from message */
        send(producer, &m); /* send back empty reply */
        consume_item(item); /* do something with the item */
    }
}