munotes®

Making Threads, and Waiting for Them

Get access to whole semester resourcesSemester Pass

Chapter Twenty-Seven

Syllabus topic Module 1, "Processes - Threads - Overview"; Computer Science Practical 3, Module 1, "Practice thread creation and basic thread lifecycle using standard libraries"

Pages 105 to 108 of 452

In one line

pthread_create starts a new flow of execution inside this process, and pthread_join waits for one to finish.

The life cycle, which is what the practical asks for

A thread has four states in its life and each has a call or an event that moves it on.

StateReached byLeft by
Created, or readypthread_create returnsthe scheduler choosing it
Runningbeing chosenblocking, being preempted, or finishing
Blockedwaiting for a lock, a condition, input or outputthe thing it waited for
Terminatedreturning from its function, or pthread_exitbeing joined, which releases its record

A terminated thread that nobody joins is the thread version of Chapter twenty one's zombie. Its stack and its record stay allocated until somebody joins it. A program that creates threads in a loop and never joins them runs out of memory, and the two ways to avoid that are to join every thread or to detach it, which tells the library that nobody will ever join it and it may be cleaned up the moment it ends.

The five calls

CallWhat it does
pthread_create(&id, attr, function, argument)start a thread running function(argument)
pthread_join(id, &result)wait for that thread, and collect what it returned
pthread_exit(value)end this thread with that value
pthread_detach(id)nobody will join this one; clean it up when it ends
pthread_self()which thread am I

The function takes one void and returns one void . That is the whole interface, and it is why every real program passes a pointer to a structure when it needs more than one argument.

Compile with -pthread. Without it the program may link and then behave strangely, because -pthread sets a compiler flag as well as adding the library.

Ten threads, made and joined

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <pthread.h>

#define HOW_MANY 10

struct job {
    int  number;
    long answer;
};

static void *work(void *argument)
{
    struct job *j = argument;

    j->answer = (long)j->number * j->number;
    printf("thread %d computed %ld\n", j->number, j->answer);
    return NULL;
}

int main(void)
{
    pthread_t id[HOW_MANY];
    struct job jobs[HOW_MANY];

    for (int i = 0; i < HOW_MANY; i++) {
        jobs[i].number = i + 1;
        if (pthread_create(&id[i], NULL, work, &jobs[i]) != 0) {
            perror("pthread_create");
            return 1;
        }
    }
    long total = 0;
    for (int i = 0; i < HOW_MANY; i++) {
        pthread_join(id[i], NULL);        /* wait, in order */
        total += jobs[i].answer;
    }
    printf("every thread joined, and the total is %ld\n", total);
    return 0;
}
$ gcc -std=c17 -Wall -Wextra -pthread -o ten ten.c
$ ./ten | grep thread | sort | tail -2
thread 8 computed 64
thread 9 computed 81
$ ./ten | grep total
every thread joined, and the total is 385
$ ./ten | grep total
every thread joined, and the total is 385
munotes.in105

Making Threads, and Waiting for Them

The total is the same every run and the order of the lines is not. The sum of the squares from one to ten is 385 whatever order the threads ran in, because each thread wrote to its own job. That is what makes this program correct, and Chapter thirty four is the same program written so that it is not.

The order really does change

$ for i in $(seq 20); do ./ten | head -1; done | sort -u > firsts.txt
$ n=$(wc -l < firsts.txt)
$ [ "$n" -gt 1 ] && echo "over twenty runs, $n different threads reached the screen first"
over twenty runs, 3 different threads reached the screen first

Three runs of the same program, and the thread that reached the screen first was not always the same. Nothing in the program decides that. It is the scheduler, and a program that depends on which thread runs first is wrong even when it works.

Counting the threads from outside

The kernel can be asked how many threads a process has while it is running.

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

static void *wait_a_while(void *unused)
{
    (void)unused;
    sleep(2);
    return NULL;
}

int main(void)
{
    pthread_t id[4];

    for (int i = 0; i < 4; i++) {
        pthread_create(&id[i], NULL, wait_a_while, NULL);
    }
    FILE *f = fopen("/proc/self/status", "r");
    char line[128];
    while (fgets(line, sizeof line, f)) {
        if (strncmp(line, "Threads:", 8) == 0) {
            printf("%s", line);
        }
    }
    fclose(f);
    for (int i = 0; i < 4; i++) {
        pthread_join(id[i], NULL);
    }
    return 0;
}
$ gcc -std=c17 -Wall -Wextra -pthread -o count-threads count-threads.c
$ ./count-threads
Threads:	5

Five: the four that were created and the one that created them. The first thread of a process is a thread like any other, and main runs on it. There is no such thing as a process with no threads.

Passing an argument, and the trap in it

Passing the address of a loop variable to every thread is the commonest thread bug there is.

#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <pthread.h>

static void *show(void *argument)
{
    int *n = argument;

    printf("%d ", *n);
    return NULL;
}

int main(void)
{
    pthread_t id[4];

    for (int i = 0; i < 4; i++) {
        pthread_create(&id[i], NULL, show, &i);   /* the SAME address, four times */
    }
    for (int i = 0; i < 4; i++) {
        pthread_join(id[i], NULL);
    }
    printf("\n");
    return 0;
}
$ gcc -std=c17 -Wall -Wextra -pthread -o wrong wrong.c
$ ./wrong
4 4 4 4

Four threads, and every one of them printed the same number. They were all given the address of i, which by the time they ran had reached 4. The fix is what ten.c did: give each thread a pointer to something of its own.

munotes.in106

Making Threads, and Waiting for Them

Distinctions that carry marks

forkpthread_create
Createsa process with its own address spacea thread sharing this one
The new one startsat the return from forkat the function given to it
Returnstwiceonce
Costhighlow
Collected withwaitpthread_join
Uncollected leavesa zombie processa thread record and its stack
pthread_joinpthread_detach
Waitsyesno
Gets the return valueyesno
After itthe record is releasedthe record is released when the thread ends
Use whenyou need the answer or the orderingyou do not care when it finishes

What it does not mean

A thread function is not main. It takes one void and returns one void , and returning from it ends only that thread.

pthread_exit in main does not end the process. It ends the first thread and lets the others run on, which is occasionally what you want and usually a surprise.

Joining is not synchronisation between running threads. It waits for one to finish. Two threads that must co-ordinate while both are running need Chapters thirty seven to forty two.

Quick revision

  • pthread_create(&id, attr, function, argument) starts a thread; pthread_join(id, &result)

waits for it and collects its value.

  • The function takes one void and returns one void . Pass a structure for more.
  • Compile with -pthread.
  • Life cycle: created, running, blocked, terminated. A terminated thread's record survives until

it is joined or it was detached: the thread version of a zombie.

  • The first thread of a process runs main, so a process with four created threads reports five.
  • The order in which threads run is the scheduler's business and changes between runs. A

program that depends on it is wrong.

  • Never pass the address of the loop variable to every thread: they all see its final value.

Test yourself

  1. Which calls create a thread and wait for one? pthread_create and pthread_join.
  2. What is the signature of a thread function? It takes one void * and returns one

void *.

  1. What happens to a thread nobody joins? Its record and stack stay allocated, like a zombie

process. Join it, or detach it so the library cleans it up when it ends.

  1. A process creates four threads. How many does the kernel report and why? Five. The thread

running main is a thread like any other. 5. Four threads are given &i, the address of the loop variable, and all print 4. Why, and what is the fix? They share one variable, which had reached its final value before they ran. Give each thread a pointer to its own data.

munotes.in107

Making Threads, and Waiting for Them

  1. Why does the total in ten.c never change while the order of the lines does? Each thread

wrote to its own structure, so nothing was shared and no result depended on the order. Only the moment each thread reached the screen depended on the scheduler.

munotes.in108

The rest of this subject

These notes are cut from the University's printed syllabus. Open the syllabus itself, or the past papers, for the same subject.

Issue
Done!