Operating systems

Concurrency: An Introduction

Prashanth L.A.

2023-10-02

Lecture 26

The second easy piece begins

Thread

What is a thread?

Multi-threaded program

Each thread has:

but shares - rest of address space (heap, static global, code section too)

Context switch

Threaded Address Space

Recall

Code segment is where instructions live

Heap segment contains malloc’d data dynamic data structures (grows downward)

Stack segment contains local variables arguments to routines, return values, etc (grows upward)

There will be one stack per thread

Coding with threads: An example

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

#include "common.h"
#include "common_threads.h"

void *mythread(void *arg) {
    printf("%s\n", (char *) arg);
    return NULL;
}

int main(int argc, char *argv[]) {                    
    if (argc != 1) {
    fprintf(stderr, "usage: main\n");
    exit(1);
    }

    pthread_t p1, p2;
    printf("main: begin\n");
    Pthread_create(&p1, NULL, mythread, "A"); 
    Pthread_create(&p2, NULL, mythread, "B");
    // join waits for the threads to finish
    Pthread_join(p1, NULL); 
    Pthread_join(p2, NULL); 
    printf("main: end\n");
    return 0;
}

Thread execution: Possible run 1

Thread execution: Possible run 2

Thread execution: Possible run 3

Threads with shared data: Example 2

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

#include "common.h"
#include "common_threads.h"

int max;
volatile int counter = 0; // shared global variable

void *mythread(void *arg) {
    char *letter = arg;
    int i; // stack (private per thread) 
    printf("%s: begin [addr of i: %p]\n", letter, &i);
    for (i = 0; i < max; i++) {
    counter = counter + 1; // shared: only one
    }
    printf("%s: done\n", letter);
    return NULL;
}
                                                                             
int main(int argc, char *argv[]) {                    
    if (argc != 2) {
    fprintf(stderr, "usage: main-first <loopcount>\n");
    exit(1);
    }
    max = atoi(argv[1]);

    pthread_t p1, p2;
    printf("main: begin [counter = %d] [%x]\n", counter, 
       (unsigned int) &counter);
    Pthread_create(&p1, NULL, mythread, "A"); 
    Pthread_create(&p2, NULL, mythread, "B");
    // join waits for the threads to finish
    Pthread_join(p1, NULL); 
    Pthread_join(p2, NULL); 
    printf("main: done\n [counter: %d]\n [should: %d]\n", 
       counter, max*2);
    return 0;
}

What to expect

prompt> gcc -o main main.c -Wall -pthread; ./main main: begin (counter = 0)
A: begin
B: begin
A: done
B: done
main: done with both (counter = 20000000)

What may really happen

Scenario 1

prompt> ./main
main: begin (counter = 0)
A: begin
B: begin
A: done
B: done
main: done with both (counter = 19345221)

Scenario 2

prompt> ./main
main: begin (counter = 0)
A: begin
B: begin
A: done
B: done
main: done with both (counter = 19221041)

Heart of the matter

mov 0x8049a1c, %eax
add $0x1, %eax
mov %eax, 0x8049a1c
Two threads
counter = counter + 1 (default is 50)
Expected result: 52

Aside: Key concurrency terms

Term Meaning
Critical section is a piece of code that accesses a shared resource, usually a variable or data structure.
Race condition arises if multiple threads of execution enter the critical section at roughly the same time; both attempt to update the shared data structure, leading to a surprising (and perhaps undesirable) outcome.
Indeterminate program consists of one or more race conditions; non-deterministic output
Mutual exclusion primitives guarantees that only a single thread ever enters a critical section

Critical section