0% found this document useful (0 votes)
12 views23 pages

Apt04 2024S2

This document provides an overview of advanced programming techniques focused on working with threads in Linux, covering fundamental concepts, thread management, synchronization, and best practices. It introduces the Pthreads library, thread life cycle, mutexes, and condition variables, along with examples to illustrate their usage. The content also emphasizes debugging and resource management for thread-safe programming.

Uploaded by

minhtrongc31120
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views23 pages

Apt04 2024S2

This document provides an overview of advanced programming techniques focused on working with threads in Linux, covering fundamental concepts, thread management, synchronization, and best practices. It introduces the Pthreads library, thread life cycle, mutexes, and condition variables, along with examples to illustrate their usage. The content also emphasizes debugging and resource management for thread-safe programming.

Uploaded by

minhtrongc31120
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 23

ADVANCED

PROGRAMMING
TECHNIQUES

VNU - UNIVERSITY of ENGINEERING & TECHNOLOGY


LECTURE 4: Working with Threads in Linux

CONTENTS

> Introduction
> Fundamental concepts
> Creating and managing threads
> Synchronization
> Best practices
Introduction
Process 1 Process 2
Process 1 Process 2 Thread 1 Thread 2 Thread 3 Thread 4 Thread 5 Thread 6

PC1 PC2
PC1 PC4
PC3 PC6
PC2 PC5

CPU CPU

> Process-based concurrency:


▪ Expensive context switching (high resource consumption + limited sharing).
▪ Slow IPC (requires system calls & data copying).

> The need for finer-grained, more efficient concurrency → threads:


▪ Lightweight execution units within a process.
▪ Threads of the same process share its memory space & resources, enabling:
✓ Faster context switching: only thread-specific context are involved.
✓ Efficient inter-thread communication: direct access to shared memory.
Logical View of Multi-Threaded Process
Thread 1 (main thread)
Stack 1 Pool of peers
Thread 1 context: T2
Data registers
Condition codes T4 Thread 3 (peer thread)
T1
Stack pointer (SP1) shared code, data Stack 3
Program counter (PC1)
and kernel context Thread 3 context:
Data registers
Condition codes
T5 T3 Stack pointer (SP3)
Program counter (PC3)

> Multiple threads run within the context of a single process


▪ Each thread has its own separate thread context
✓ Thread ID, PC, SP, GP registers, condition codes: own logical control flow.
✓ Each thread has its own stack, but NOT protected from other threads.
▪ All threads share the the remaining process context
✓ Thread ID, PC, SP, GP registers, condition codes: own logical control flow.
Execution of Concurrent Threads
> Two threads are concurrent if their flows overlap in time.
▪ Otherwise, they are sequential.

> The execution of concurrent threads …


▪ on single-core processor: simulates parallelism via time slicing.
▪ on multi-core processor: can have true parallelism.

Thread A Thread B Thread C Thread A Thread B Thread C

Time

Single core Core 1 util.


utilization
Core 2 util.
Summary: Threads vs. Processes

> Similarities
▪ Each has its own logical control flow
▪ Each can run concurrently with others (possibly on different cores)
▪ Each is context switched

> Differences
▪ Threads share all code and data (except local stacks)
✓ Processes (typically) do not
▪ In Linux threads are more lightweight:
✓ ~20K cycles to create and reap a process
✓ ~10K cycles (or less) to create and reap a thread
Thread Life Cycle
> Basic states:
▪ New (created): the thread is created but has not started execution yet.
▪ Runnable (ready) – the thread is ready to run but is waiting for CPU time.
▪ Running: the thread is actively executing on a processor.
▪ Blocked (waiting) – the thread is waiting for an event (e.g., I/O, mutex lock).
▪ Terminated (dead) – The thread has finished execution.

scheduled

Done/
Cancel
New Runnable Running Termination

Blocked

preempted
Introduction to the Pthreads Library
> Standardized in the IEEE POSIX 1003.1c standard and contains
~60 API functions for manipulating threads from C programs.
▪ Implemented with a pthread.h header file in your C program.
▪ Naming conventions: all identifiers in the library begin with “pthread_”.
▪ Programs using pthreads API must be compiled with -pthread option.

> This course will focus on 3 function classes:


▪ Thread management: functions that work directly on threads, e.g. creating,
detaching, joining, etc.
▪ Mutexes (mutual exclusion): functions that deal with synchronization, e.g.
mutext initialization/destruction, locking/unlocking, timed lock, etc.
▪ Condition variables: functions for communications between threads that
share a mutex.
Creating Threads in Linux
> Initially, your main program comprises a single, default thread.
All other threads must be explicitly created by the programmer:
Returns 0 on success; non-zero Pointer to a pthread_t variable
error code on failure. where the thread ID will be stored

int pthread_create(pthread_t *thread, const pthread_attr_t


*attr, void *(*start_routine)(void *), void *arg);

Pointer to a thread Pointer to the function A single argument passed to


attribute object (NULL the thread will execute routine (NULL if not needed)
for default attributes)

▪ The thread that calls pthread_create() continues execution with the next
statement that follows the call.
▪ After a call to pthread_create(), a program has no guarantees about which
thread will next be scheduled to use the CPU.
Terminating Threads in Linux
> A thread terminates in one of the following ways:
▪ It completes its start function (via return).
▪ Another thread cancels it via pthread_cancel().
▪ Any thread in the process calls exit(), or the main thread returns.
▪ Invocation of pthread_exit() from within any function it calls.

void pthread_exit(void *retval);

Pointer to the return value that can be obtained


in another thread by calling pthread_join()

> When the main thread calls pthread_exit():


▪ it terminates itself while other threads in the process to continue running;
▪ the process remains active until all threads have exited.
Joining a Thread

int pthread_join(pthread_t thread, void **retval);

Returns 0 on success; positive The thread ID of the Pointer to store the return value
error number on failure. thread to wait for of the terminated thread (NULL
if the return value is not needed)

> pthread_join() blocks the calling thread and enables kernel


resource reclamation upon target thread termination.
▪ If that thread has already terminated, pthread_join() returns immediately.
▪ Joining a thread that has been already joined can cause unpredictable results
▪ Joining joinable threads is critical to reclaim their resources upon termination
✓ otherwise, they will produce the thread equivalent of zombie processes.

> pthread_join() allows any thread to join any other thread


within the same process (threads are peers).
▪ Processes are hierachical: only a parent can invoke wait() on a child.
Detaching a Thread

int pthread_detach(pthread_t thread);

Returns 0 on success; positive The thread ID of


error number on failure. the target thread

> pthread_detach() turns a thread from joinable into detached.


▪ The detached thread runs independently and cannot be joined.
▪ When it terminates, its resources are automatically released by the system
without requiring another thread to call pthread_join().
▪ A thread can detach itself using pthread_detach(pthread_self());
▪ Useful for background tasks that run without interacting with other threads.

> Detached threads it will still be terminated if another thread calls


exit() or if the main thread returns.
▪ Such actions terminate all threads in the process, regardless of joinability.
Example: Multi-Threaded Global Sharing Program

#include<stdio.h> void *threadfun(void *arg)


#include<pthread.h> {
int loc,j,loops = *((int *) arg);
int glob = 0;
for (j = 0; j < loops; j++) {
void main() loc = glob;
{ loc++;
pthread_t t1, t2; glob = loc;}
int loops = 10000; }

pthread_create(&t1, NULL, threadfun, &loops);


pthread_create(&t2, NULL, threadfun, &loops);
pthread_join(t1, NULL);
pthread_join(t2, NULL);

printf("glob = %d\n", glob);


}
Multi-Threaded Global Sharing Program Execution

▪ Shared variables are handy...


▪ … they can introduce nasty
synchronization errors.

▪ Why not change this to


“glob++;”?
▪ Compiler would convert it into
machine code whose steps
may still be unchanged.
Example: Assembly Code for the Incremental Loop

for (j = 0; i < loops; j++)


glob++;

Asm code for thread i


movq (%rdi), %rcx
testq %rcx,%rcx
Hi : Head
jle .L2
movl $0, %eax
.L3:
movq cnt(%rip),%rdx Li : Load glob
addq $1, %rdx Ui : Update glob
movq %rdx, cnt(%rip) Si : Store glob
addq $1, %rax
cmpq %rcx, %rax
Ti : Tail
jne .L3
.L2:
Mutex (Mutual Exclusion) Variables
> A mutex variable acts as a “lock”, safeguarding shared data
during concurrent writes.
▪ Exclusive access to a mutex is granted to only one thread at any given time,
with other threads being blocked until the mutex is unlocked.
✓ Threads must “take turns” accessing protected data.
▪ All threads accessing shared data must use the corresponding mutex.
✓ E.g. if only 1 of 4 threads uses mutex, shared data can still be corrupted.

> A typical sequence in the use of a mutex is as follows:


▪ Create and initialize a mutex variable
▪ Several threads try to lock the mutex, only 1 succeeds & gains ownership.
▪ The owner thread performs some set of actions
▪ The owner unlocks the mutex
▪ Another thread acquires the mutex and repeats the process
▪ Finally, the mutex is destroyed when it is no loger needed.
Implementing Mutexes in Linux
> Mutex variables must be initialized before they can be used:
▪ Statically, when it is declared with type pthread_mutex_t.
✓ E.g. pthread_mutex_t mymutex = PTHREAD_MUTEX_INITIALIZER;
▪ Dinamically, using pthread_mutex_init().
✓ E.g. pthread_mutex_init(&mymutex, NULL);

> To free a mutex object, use: pthread_mutex_destroy().


> To lock a mutex: pthread_mutex_lock()
▪ If the mutex is already locked by another thread, this call will block the calling
thread until the mutex is unlocked.

> To unlock a mutex: pthread_mutex_unlock()


▪ Returns an error if unlocking a mutex that is not currently locked.
▪ Also returns an error if unlocking a mutex that is locked by another thread.
Example: Global Sharing with Mutexes
#include<stdio.h>
#include<pthread.h>
#include<stdio.h> void *threadfun(void *arg)
#include<pthread.h> {
int glob = 0; int loc,j,loops = *((int *) arg);
pthread_mutex_t
int glob = 0; mtx = PTHREAD_MUTEX_INITIALIZER;
for (j = 0; j < loops; j++) {
void *threadfun(void *arg)
void loc = glob;
void main()
main() {
loc++;
{
{ int loc,j,loops = *((int *) arg);
glob = loc;}
pthread_t
pthread_t t1, t2;
t1, t2; }
int
int loops,
loops =10000;
10000; for (j = 0; j < loops; j++) {
pthread_mutex_lock(&mtx);
pthread_create(&t1,
pthread_create(&t1, NULL,
NULL, loc = glob;
threadfun,
threadfun, &loops);
&loops);
pthread_create(&t2, loc++; &loops);
pthread_create(&t2, NULL,
NULL, threadfun,
threadfun, &loops);
pthread_join(t1, glob = loc;
pthread_join(t1, NULL);
NULL);
pthread_join(t2, NULL); pthread_mutex_unlock(&mtx);}
pthread_join(t2, NULL); }
printf("glob
printf("glob =
= %d\n",
%d\n", glob);
glob);
}
}
Condition Variables
> Allow threads to communicate with each other & coordinate
their actions based shared data state without busy-waiting.
▪ Threads can wait until a particular condition is met.
▪ Threads can signal condition variable to wake up 1 or all the waiting threads.

Main thread
▪ Declare and initialize global data/variables which require synchronization (e.g. "count")
▪ Declare and initialize a condition variable object and an associated mutex.
▪ Create threads A and B to do work
Thread A Thread B
▪ Do work until certain condition occurs ▪ Do work
▪ Lock associated mutex & check global ▪ Lock associated mutex
variable value. ▪ Change the value of the global variable.
▪ Wait for signal from Thread B. ▪ Check the global variable, if condition is
▪ Wake up when signalled. met, signal Thread A.
▪ Explicitly unlock mutex ▪ Unlock mutex.
▪ Continue ▪ Continue
Main threadd
▪ Join/continue
Implementing Condition Variables in Linux

> Statically allocated condition variables:


▪ pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

> Signal/wake up thread(s) waiting on the condition variable:


▪ int pthread_cond_signal(pthread_cond_t *cond);
▪ int pthread_cond_broadcast(pthread_cond_t *cond);
▪ Should be called after mutex is locked, and must unlock mutex in order for
pthread_cond_wait() routine to complete.

> Blocks the calling thread until the specified condition is signalled:
▪ int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t
*mutex);
✓ Automatically unlock the mutex specified by mutex;
✓ block the calling thread until receiving the condition variable cond;
✓ Automatically relock mutex.
Example: Condition Variables
#include<stdio.h>
#include<pthread.h>

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;


pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int ready = 0; // Shared condition variable *arg){
void *prodfun(void
pthread_mutex_lock(&lock);
void main() ready = 1;
{ printf("Producer: Ready, signal consumer\n");
pthread_t prod, cons; pthread_cond_signal(&cond);
pthread_mutex_unlock(&lock);}
pthread_create(&prod, NULL, prodfun, NULL);
pthread_create(&cons, NULL, consfun, NULL);
pthread_join(prod, NULL); void *consfun(void *arg){
pthread_join(cons, NULL); pthread_mutex_lock(&lock);
pthread_mutex_destroy(&lock); while (!ready){
pthread_cond_destroy(&cond); printf("Consumer: Waiting...\n");
pthread_cond_wait(&cond, &lock);}
}
printf("Consumer: Data received.\n");
pthread_mutex_unlock(&lock);}
Debugging and Best Practices

> Using gdb for thread inspection and debugging.


> Use Valgrind's Helgrind tool for data race detection.
> Proper resource management.
> Thread-safe programming.
▪ Write thread-safe code, use thread-safe API only (those can be executed by
multiple threads concurrently without any data corruption or race conditions.)

> Minimizing thread overhead.


NEXT LECTURE

[Flipped class] Introduction to parallel programming


> Pre-class
▪ Study pre-class materials on Canvas

> In class
▪ Reinforcement/enrichment discussion

> Post class


▪ Homework
▪ Preparation for Lab 1
▪ Consultation (if needed)

You might also like