threads Flashcards

1
Q

Thread Motivation

A
  • Processes are expensive to create and maintain
    – Context switch overhead between processes
    – Process synchronization cumbersome
    – Communication between processes must be done through an external structure
  • Files
  • Pipes
  • Sockets
  • Shared memory
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Processes and Threads

A
  • Process considered to be heavyweight
    – Ownership of memory, files, other resources
  • Threads
    – A thread can be seen as lightweight process
    – A unit of execution associated with a particular process, using many of the process’ resources
    – Multithreading
  • Allowing multiple threads per process
    – Benefits of multithreading
  • Responsiveness (minimize time, concurrency)
  • Resource sharing (i.e., shared memory)
  • Economy (creation, switch)
  • Scalability (explore multi-core CPUs)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Process Properties

A
  • Each process has its own:
    – Program Counter (PC)
    – Stack
    – Stack Pointer (SP)
    – Address space
  • Processes may share:
    – Open files
    – Pipes
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

Thread View

A
  • Operating System Perspective
    – An independent stream of instructions that can be scheduled to be run by the OS
  • Programmer Perspective
    – Can be seen just as a “function” that executes independently from the main program
    – A single stream of instructions in a program
  • Multiple threads can share a process
    – Multiple flows of control within a process
    – Share code and data space of process
    – Lower overhead, avoid context switches
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Thread Execution Environment

A

The process is the execution environment for a family of threads
– A group of threads share the same resources (files, memory space, etc.)
* Since threads are associated with their process, they are able to use the resources belonging to the process and duplicate only the required resources, such as the PC, stack, and SP, needed by the OS to schedule threads independently from their processes

– This makes creating and switching between threads belonging to same process very simple and much more efficient than creating or switching between processes
* Although switching for threads belonging to different processes is still as complex as classic process switch

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Thread Properties

A
  • Each thread has its own:
    – Program Counter (PC)
    – Stack
    – Stack Pointer (SP)
    – Registers
    – Scheduling properties
    – Set of pending/blocked signals
    – Thread specific data
  • Threads share:
    – Address space
  • Variables
  • Code
    – Open files

Since a thread exists within a process, if a process terminates, so does the thread

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Thread Consequences

A
  • Since threads within the same process share resources
  • Changes made by one thread to shared system resources (such as closing a file) will be seen by all other threads
    – Any files opened inside a thread will remain open (unless explicitly closed) even after the thread is terminated
  • Two pointers having the same value point to the same data
  • Reading and writing to the same memory locations is possible
    – Therefore requires explicit synchronization by the programme
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

POSIX Threads

A
  • The most commonly used thread package on Linux is POSIX threads (also known as pthreads)
    – Standards-based API to create, manage, and synchronize threads
  • Thread package includes library calls for
    – Thread management (creation, destruction)
    – Mutual exclusion (synchronization, short-term locking)
    – Condition variables (waiting on events of unbounded duration)
  • A run-time system manages threads in a transparent manner so that the user is unaware
    – Robust programs should not depend upon threads executing in a specific order
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

The pthreads Library

A

The pthread.h library includes the following operations:
– pthread_create create a new thread
– pthread_detach detach a thread, to release its resources when thread terminates
– pthread_exit terminate calling thread, without terminating process
– pthread_join wait for a specified thread to terminate
– pthread_equal compare thread IDs
– pthread_kill send a specified signal to a thread
– pthread_self obtain the ID of the calling thread

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Creating Threads

A

int pthread_create(pthread_t tid, const pthread_attr_t tattr, void (start_routine)(void *), void *arg);
– tid: an unsigned long integer that indicates a thread’s id
– tattr: attributes of the thread – usually NULL
– start_routine: the name of the function the thread starts executing
– arg: the argument to be passed to the start routine – only one

  • pthread_create( tid, NULL, void (*start_routine), void *arg);
  • After this function gets executed, a new thread has been created and is executing the function indicated by start_routine
  • Once created, threads are peers, and may create other threads
  • There is no implied hierarchy or dependency between threads
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Joining Threads

A

Joining is one way to accomplish synchronization between threads where the process waits for all threads to complete
– We call the function pthread_join() once for each thread

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Wait for Completion of a Thread

A

int pthread_join(thread_t tid, void **status);
– tid: identification of the thread to wait for
– status: the exit status of the terminating thread – can be NULL

  • The process that calls this pthread_join function blocks its own execution until the thread indicated by tid terminates its execution
    – Finishes the function it started with, or
    – Issues a pthread_exit() command
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Exiting a Thread

A
  • pthreads exist in user space and are seen by the kernel as a single process
    – If one issues an exit() system call, all the threads are terminated by the OS
    – If the main() function exits, all of the other threads are terminated
  • To have a thread exit, use pthread_exit()

void pthread_exit(void *status);
– status: the exit status of the thread – passed to the status variable in the pthread_join() function of a thread waiting for this one

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Detaching a Thread

A
  • Indicates that system resources for the specified thread should be reclaimed when thread ends
    – If the thread has already ended, resources are reclaimed immediately
    – This routine does not cause the thread to end!
  • Threads are detached
    – After a pthread_detach() call
    – After a pthread_join() call
    – If a thread terminates and PTHREAD_CREATE_DETACHED attribute was set on creation
  • Failure to join or detach threads – memory and other resources will leak until the process ends

int pthread_detach(pthread_t tid);
– tid: identification of the thread to detach

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Memory in Processes vs Threads

A
  • Each thread will have its own stack, but all the threads in a process will share the heap.
  • Threads are sometimes called lightweight processes because they have their own stack but can access shared data.
  • Because threads share the same address space as the process and other threads within the process, the operational cost of communication between the threads is low, which is an advantage.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Thread Safety

A
  • The threads for a process share the entire address space of the process
  • This means, threads can modify global variables, access open file descriptors, etc.
    – Be careful when reusing variables passed by reference
  • So, we need to ensure that multiple threads do not interfere with each other – synchronize thread execution
  • A program or function is said to be thread safe if it can be called from multiple threads without unwanted interaction between the threads
    – That is, it should be able to execute multiple threads simultaneously without “clobbering” shared data or creating “race” conditions
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

Race condition

A
  • A race condition occurs when two or more threads can access shared data and they try to change it at the same time.
  • Because the thread scheduling algorithm can swap between threads at any time, you don’t know the order in which the threads will attempt to access the shared data.
  • Therefore, the result of the change in data is dependent on the thread scheduling algorithm, i.e. both threads are “racing” to access/change the data.
18
Q

Thread-Safe Functions

A
  • Not all functions can be called from threads
    – Many use global/static variables
    – Many functions have thread-safe replacements with _r suffix
  • Safe:
    – ctime_r(), gmtime_r(), localtime_r(), rand_r(), strtok_r()
  • Not safe:
    – ctime(), gmtime(), localtime(), rand(), strtok()
  • Could use semaphores to protect access, but this generally results in poor performance
19
Q

Data Race Example

A
  • Also called a critical section problem
  • A race condition or data race occurs when
    – Two processors (or two threads) access the same variable, and at least one does a write
    – The accesses are concurrent (not synchronized) so that could happen simultaneously
20
Q

Synchronizing Threads

A

3 basic synchronization primitives / constructs / operations

– Mutex Locks (like a gatekeeper)
* Control thread’s access to the data with simple mutual exclusion

– Condition Variables
* More complex synchronization
* Let threads wait until a user-defined condition becomes true (i.e., based on the value of the data
* Removes polling requirement

– Semaphores
* Signal-based synchronization
* Allows sharing (not wait unless semaphore = 0)
* Access to data granted/blocked based on semaphore value

21
Q

Mutex Locks definition

A

Mutex (mutual exclusion) is a special type of variable used to restrict access to a critical section to a single thread at a time
– Guarantee that one thread “excludes” all other threads while it executes the critical section
– When a thread waits on a mutex/lock, CPU resource can be used by others

22
Q

mutex locks implementation

A
  • A mutex lock is created like a normal variable
    – pthread_mutex_t mutex;
  • Easier to use than standard semaphores
  • Only the thread (say tid = 1) that locks a mutex can unlock it
  • Mutexes are often declared as global variables
  • Mutexes must be initialized before being used
    – A mutex can only be initialized once

int pthread_mutex_init(pthread_mutex_t *mp, const pthread_mutexattr_t *mattr);
* mp: a pointer to the mutex lock to be initialized
* mattr: attributes of the mutex – usually NULL

23
Q

Locking a Mutex

A

To ensure mutual exclusion to a critical section, a thread should lock a mutex

1.When locking function is called, it does not return until the current thread owns the lock
2.If the mutex is already locked, calling thread blocks
3.If multiple threads try to gain lock at the same time, the return order is based on priority of the threads
* Higher priorities return first
* No guarantees about ordering between same priority threads

int pthread_mutex_lock(pthread_mutex_t *mp);
* mp: mutex to lock

24
Q

Unlocking a Mutex

A

When a thread is finished within the critical section, it needs to release the mutex
– Calling the unlock function releases the lock
– Then, any threads waiting for the lock compete to get it
– Very important to remember to release mutex

int pthread_mutex_unlock(pthread_mutex_t *mp);
* mp: mutex to unlock

25
Q

Condition Variables

A
  • Condition variables provide yet another way for threads to synchronize. While mutexes implement synchronization by controlling thread access to data, condition variables allow threads to synchronize based upon the actual value of data.
  • Without condition variables, the programmer would need to have threads continually polling (possibly in a critical section), to check if the condition is met.
  • This can be very resource-consuming since the thread would be continuously busy in this activity. A condition variable is a way to achieve the same goal without polling.
  • A condition variable is always used in conjunction with a mutex lock
  • A condition variable is an explicit queue (a data structure) that threads can put themselves in when some state of execution (i.e., some condition) is not as desired (by waiting on the condition)
  • Another thread, when it changes the condition, can wake one (or more) of those waiting threads to allow them to continue (by signaling on the condition)
  • A condition variable has two operations associated with it:
    – The wait() call is executed when a thread wishes to put itself to sleep
    – The signal() call is executed when a thread has changed something in the program and now wants to wake a sleeping thread waiting on this condition
26
Q

Spurious Wakeup

A

For performance reasons, the POSIX API allows the OS to wake up your thread even if the condition has not been fulfilled
– This is called spurious wakeup, a complication that arises from the use of condition variables as provided by certain multithreading APIs where even after a condition variable appears to have been signaled from a waiting thread’s point of view, the condition that was awaited may still be false
– A thread might be awoken from its waiting state even though no thread signaled the condition variable
– For correctness it is necessary, then, to verify that the condition is indeed true after the thread has finished waiting
– Because spurious wakeup can happen repeatedly, this is achieved by waiting inside a loop that terminates when the condition is true

27
Q

Semaphores

A
  • Semaphores are a good way to learn about synchronization, but they are not as widely used, in practice, as mutexes and condition variables.
  • A semaphore is a data structure used to help threads work together without interfering with each other.
  • pthreads allow the specific creation of semaphores
    – Semaphore is an integer variable and can be initialized to any value
  • Can do increments and decrements of semaphore value
    – Thread blocks if semaphore value is less than or equal to zero when a decrement is attempted
    – As soon as semaphore value is greater than zero, one of the blocked threads wakes up and continues
  • No guarantees as to which thread this might be
28
Q

Creating Semaphores

A
  • Semaphores are created like other variables
    – sem_t semaphore; (POSIX semaphores have type sem_t)
  • Semaphores must be initialized

int sem_init(sem_t *sem, int pshared, unsigned int value);
* sem: the semaphore value to initialize
* pshared: share semaphore across processes – usually 0
* value: the initial value of the semaphore

29
Q

Decrementing a Semaphore

A

int sem_wait(sem_t *sem);
– sem: semaphore to try and decrement

  • If the semaphore value is greater than 0, the sem_wait call return immediately
    – Otherwise it blocks the calling thread until the value becomes greater than 0
  • The sem_wait() atomic operation has the following semantics
    sem_wait(S)
    {
    while S <= 0 wait in a queue;
    S–;
    }
30
Q

Incrementing a Semaphore

A

int sem_post(sem_t *sem);
– sem: the semaphore to increment

  • Increments the value of the semaphore by 1
    – If any threads are blocked on the semaphore, they will be unblocked
  • Doing a post (i.e., sem_post) to a semaphore always raises its value – even if it shouldn’t!
  • The sem_post() atomic operation has the following semantics

sem_post(S)
{
S++;
Wake up a thread that waits in the queue;
}

31
Q

Destroying a Semaphore

A

int sem_destroy(sem_t *sem);
– sem: the semaphore to destroy

  • Destroys the semaphore at the address pointed to by sem
  • Only a semaphore that has been initialized by sem_init should be destroyed using sem_destroy
  • Destroying a semaphore that other processes or threads are currently blocked on produces undefined behavior
    – Same with using a semaphore that has already been destroyed
32
Q

What is the primary advantage of using threads in a multi-threaded application?

A

Threads allow for concurrent execution and better resource utilization

33
Q

Which of the following is true about thread synchronization?

A

Thread synchronization is used to ensure that threads do not interfere with each other when accessing shared resources

34
Q

A condition variable is used to wait until a particular condition is true. Condition variables must be used in conjunction with a mutex lock. (t/f)

A

True

35
Q

Suppose P, Q and R are co-operating processes satisfying Mutual Exclusion condition. If the process Q is executing in its critical section then, ……………..

A

‘P’ does not executes in critical section, ‘R’ does not executes in critical section

36
Q

Which are true of the (counting) semaphore operation called “wait()”?

it decrements the value of the semaphore

it increments the value of the semaphore

if the value becomes negative, the calling process is blocked

if the value is not positive, another process is unblocked

A lower-level mutual exclusion mechanism is needed, to ensure that the fetching and storing of the semaphore value are done atomically.

A

it decrements the value of the semaphore

if the value becomes negative, the calling process is blocked

A lower-level mutual exclusion mechanism is needed, to ensure that the fetching and storing of the semaphore value are done atomically.

37
Q

Which of the following are true of the pthread_mutex_lock() operation?

it takes a reference to a condition variable as a parameter
it takes a reference to a mutex as a parameter
it is intended to provide mutual exclusion
it is intended for waiting for a condition or event

A

it takes a reference to a mutex as a parameter
it is intended to provide mutual exclusion

38
Q

If one thread of a process is holding the lock on a mutex, and another thread of the process attempts to lock the mutex, the whole process is blocked. (t/f)

A

false

39
Q

In a system that supports multithreaded processes, which of the following are likely to be associated with an individual thread (i.e., different for different threads within the process)?

Execution state (running, ready, etc.)

Virtual address space

Saved context (when not running)

Protections/permissions to access system resources

Execution stack

Set of accessible open files

A

Execution state (running, ready, etc.)
Saved context (when not running)
Execution stack
Set of accessible open files

40
Q

Because the thread scheduling algorithm can swap between threads at any time, you must program the order in which the threads will attempt to access the shared data

A

false

41
Q

A “strong” semaphore is one that always achieves mutual exclusion. (t/f)

A

true

42
Q

Match the following

Counting Mechanism for Resource Allocation

Used to Coordinate Threads’ Execution

Non-deterministic events

Threads make no progress because of this circular chain of dependency mechanism

An atomic increment and decrement operation is

A

Counting Mechanism for Resource Allocation
Correct match:
Semaphore

Used to Coordinate Threads’ Execution
Correct match:
Condition variables

Non-deterministic events
Correct match:
Spurious wakeup

Threads make no progress because of this circular chain of dependency mechanism
Correct match:
Deadlock

An atomic increment and decrement operation is
Correct match:
Thread-safe