Context switching is a very expensive operation (save registers !)
There is at least one thread within every process (otherwise, the process will not run :-))
A single threaded process has exactly one thread...
In other words:
Many computer vendors have developed their own thread application programming interface to allow programmers to write multi-threaded programs.
Sun Microsystems has implemented the Solaris threads API (and it is still available)
When a multi-threaded program is written on one platform, it usually cannot be compiled on another (due to differences in the APIs)
To promote portability, the POSIX thread standard was proposed and adopted.
#include <pthread.h>
cc -mt [other flags] file... -lpthread [other libraries]
Syntax:
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine, void*),
void *arg);
Input parameters:
This function can have (exactly) one parameter (or argument)
Output parameter:
Upon successful completion, pthread_create() stores the ID of the created thread in the location referenced by thread.
Return value:
Usage:
pthread_t TID; /* Thread ID - used for signaling */
pthread_attr_t attr; /* Thread attribute values for creation */
/* Clear out thread attributes (use default values) */
if (pthread_attr_init(&attr) != 0)
{ perror("Thread_Create: pthread_attr_init");
exit(1);
}
.... set various thread attributes if needed (often not)
if (pthread_create(&TID, &attr, my_proc, param) != 0)
{ perror("Thread_Create: pthread_create");
exit(1);
}
Elsewhere in the program:
void *my_proc(void *) param) <---- new thread will begin
{ execution with this function
....
}
|
This thread contents for the CPU with other processes in the system (is permanently bounded to an "LWP" - a schedule unit in the operating system kernel). Such a thread is called a bound thread.
This thread contents for the CPU with other "process scoped" threads. Such a thread is called a unbound thread.
You cannot invoke pthread_detach() (detach again ?) and pthread_join() on detached threads Detached threads run independently from other threads and cannot wait for other threads using pthread_join().
Joinable threads may call pthread_join() to wait on another thread upon its termination (usually to pass some return value to another thread - is similar to the join system call (cs351))
int pthread_attr_init(pthread_attr_t *attr)
Some access operations are conflicting and cannot be executed simulateneously
One common conflicting operations are:
Suppose the variable "balance" contains the value 1000.
"Normally", when both threads finish with their operations on the "balance" variable, the new values of "balance" will be 900.
However, it is possible that the CPUs execute the assembler instructions interleaved.... (afterall, each CPU executes the instructions independently from one another).
Hence, one possible execution ordering is the following:
CPU 1 CPU 2
--------------------- ------------------------
move.l balance, D0 (D0 = 1000)
move.l balance, D0 (D0 = 1000)
add.l #100, D0 (D0 = 1100)
sub.l #200, D0 (D0 = 800)
move.l D0, balance, (balance = 1100)
move.l D0, balance, (balance = 800)
And when both threads finish with their access of the "balance"
variable, the resulting value of balance is 800 !
From CS351, you may have learned the following common synchronization methods:
So it is no big deal that PThreads do not implement them...
(There are different way to achieve this "atomic" execution effect and will not discussed here. If you are interested, read more on "interrupt disable", "test-and-set" instructions)
pthread_mutex_t *x;
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr); Example: pthread_mutex_init(&x, NULL); /* Default initialization */
int pthread_mutex_lock(pthread_mutex_t *mutex); Example: pthread_mutex_lock(&x);
int pthread_mutex_unlock(pthread_mutex_t *mutex); Example: pthread_mutex_unlock(&x);
A common usage of mutex is to synchronize updates to shared (global) variables
Whenever a thread want to read or write a shared variable, it must enclose the operation by a "lock - unlock" pair.
Example:
The atomic execution of the mutex lock function prevents multiple threads from executing instructions that access the common variable.
A common error is to forget the unlock call... the result is "deadlock" - you program hangs (no progress)
These are very similar to mutex, except that there are 2 types of locks: read and write.
Read and write lock operations conflict, one write and another write lock operations also conflict, but 2 read lock operations can be performed at the same time
Read/Write Lock API:
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
The "test-and-set(x)" operation on a (memory) variable "x" performs the following operations atomically:
The test-and-set operation is a powerful operation that can be use to implement all shared memory synchronization primitives.
pthread_mutex_init(&x) <----> x = 0;
pthread_mutex_lock(&x) <----> Loop: if ( test-and-set(x) == 1 ) goto Loop;
pthread_mutex_unlock(&x) <----> x = 0;
int pthread_cond_init(pthread_cond_t *cond,
const pthread_condattr_t *attr);
Example:
pthread_cond_t x;
pthread_cond_init(&x, NULL);
int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex);
Atomically:
int pthread_cond_wait(pthread_cond_t *cond);
sema.phore \'sem-*-.fo-(*)r, -.fo.(*)r\ n [Gk se-ma sign, signal + ISV -phore] 1: an apparatus for visual signaling (as by the position of one or more movable arms) 2: a system of visual signaling by two flags held one in each hand
If s = 1 (up), P(s) returns immediately and the thread that executes the P(s) operation continues - this is the success case (no train in the critical section)
If s = 0 (down), P(s) blocks - the thread executing the P(s) operation will be made unrunnable (not ready to run) and put in a queue that is associated with the semaphore s.
Note: the "P" operation is the acronym for Pass - Dutch: Passeren)
Set s = 1 and if the queue that is associated with the semaphore s is not empty, then reactivate a block process from that queue
Note: V(s) always succeeds (leaving the critical section can always be done).
Note: the "V" operation is the acronym for leaVe :-) - Just kidding, you need to know Dutch to find the acronym: Verlaten in Dutch means to leave.... hence the V - remember, Dijkstra was a Dutch...)
typedef struct BIN_SEMA
{
pthread_cond_t cv; /* cond. variable used to block thread */
pthread_mutex_t mutex; /* The mutex used in association with cv */
/* The mutex variable prevents concurrent
access to the (shared) variable flag
(next variable) */
int flag; /* Semaphore state: 0 = down, 1 = up */
} bin_sema;
void BinSemaInit(bin_sema **s, int value)
{
if (value >= 1 || value < 0)
value = 1; /* make sure it's 0 or 1 */
*s = (bin_sema *) malloc(sizeof(bin_sema)); /* Create the sema struct */
pthread_mutex_init(&((*s)->mutex), NULL); /* Init mutex */
pthread_cond_init(&((*s)->cv), NULL); /* Init cond. variable */
(*s)->flag = value; /* Set flag value */
}
void BinSemaP(bin_sema *s)
{
pthread_mutex_lock(&(s->mutex)); /* Gain exclusive access to s->flag */
while (s->flag == 0) /* If flag == 1 then skip while loop */
pthread_cond_wait(&(s->cv), &(s->mutex) ); /* flag = 0 in while body */
/* Block the thread on the conditional variable cv...
Note that we must RELEASE the mutex that was locked
previously !
This thread will be restarted by another thread
that performs a V operation
*/
/* -----------------------------------------------------------
When you exit the while loop, s->flag == 1 and this thread
has successfully pass semaphore...
----------------------------------------------------------- */
s->flag = 0; /* This will cause other thread
that executes P() to block... */
pthread_mutex_unlock(&(s->mutex)); /* Done with updating flag, release
the mutex so that other threads
can access the flag variable
(they will find flag == 0 and
block, but before they can
make this decision, they need
access to "flag") */
}
void BinSemaV(bin_sema *s)
{
pthread_mutex_lock(&(s->mutex)); /* Gain exclusive access to s->flag */
pthread_cond_signal(&(s->cv)); /* Restart some thread that
was blocked on s->cv
- if there was not thread blocked on
- cv, this operation does absolutely
- nothing... */
s->flag = 1; /* Update semaphore state to Up */
pthread_mutex_unlock(&(s->mutex)); /* Done, release exclusive access */
}
Semaphores are more powerful than mutex locks - anywhere you can use a mutex to ensure synchronization, you can also use a semaphore (but not vice versa).
Here is an example of using binary semaphores to access shared variables:
Thread 1: Thread 2: BinSemaP(s); BinSemaP(s); balance = balance + 100; balance = balance - 200; BinSemaV(s); BinSemaV(s);
The famous "Reader and Writer" synchronization problem can be solved with semaphores, but not with mutexes:
share variable x Reader sums all values written by writer exactly once Use 2 semaphores: ReadSema and WriteSema BinSemaInit(ReadSema, 0); /* Block reader until writer has written */ BinSemaInit(WriteSema, 1); /* Allow writer to start first */ Reader Thread: Write thread: BinSemaP(ReadSema); BinSemaP(WriteSema); sum = sum + x; x = new_value(); BinSemaV(WriteSema); BinSemaV(ReadSema);