Upload
others
View
47
Download
0
Embed Size (px)
Citation preview
Carnegie Mellon
2Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Traditional View of a Process
Process = process context + code, data, and stack
Shared libraries
Run-time heap
0
Read/write data
Program context: Data registers Condition codes Stack pointer (SP) Program counter (PC)
Code, data, and stack
Read-only code/data
StackSP
PC
brk
Process context
Kernel context: VM structures Descriptor table brk pointer
Carnegie Mellon
3Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Alternate View of a Process
Process = thread + code, data, and kernel context
Shared libraries
Run-time heap
0
Read/write dataThread context: Data registers Condition codes Stack pointer (SP) Program counter (PC)
Code, data, and kernel context
Read-only code/data
StackSP
PC
brk
Thread (main thread)
Kernel context: VM structures Descriptor table brk pointer
Carnegie Mellon
4Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
A Process With Multiple Threads
Multiple threads can be associated with a process Each thread has its own logical control flow Each thread shares the same code, data, and kernel context Each thread has its own stack for local variables
but not protected from other threads Each thread has its own thread id (TID)
Thread 1 context: Data registers Condition codes SP1 PC1
stack 1
Thread 1 (main thread)
shared libraries
run-time heap
0
read/write data
Shared code and data
read-only code/data
Kernel context: VM structures Descriptor table brk pointer
Thread 2 context: Data registers Condition codes SP2 PC2
stack 2
Thread 2 (peer thread)
Carnegie Mellon
5Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Logical View of Threads
Threads associated with process form a pool of peers Unlike processes which form a tree hierarchy
P0
P1
sh sh sh
foo
bar
T1
Process hierarchyThreads associated with process foo
T2T4
T5 T3
shared code, dataand kernel context
Carnegie Mellon
6Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Concurrent Threads
Two threads are concurrent if their flows overlap in time
Otherwise, they are sequential
Examples: Concurrent: A & B, A&C Sequential: B & C
Time
Thread A Thread B Thread C
Carnegie Mellon
7Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Concurrent Thread Execution
Single Core Processor Simulate parallelism
by time slicing
Multi-Core Processor Can have true
parallelism
Time
Thread A Thread B Thread C Thread A Thread B Thread C
Run 3 threads on 2 cores
Carnegie Mellon
8Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Threads vs. Processes How threads and processes are similar
Each has its own logical control flow Each can run concurrently with others (possibly on
different cores) Each is context switched
How threads and processes are different Threads share all code and data (except local stacks)
Processes (typically) do not Threads are somewhat less expensive than processes
Process control (creating and reaping) twice as expensive as thread control
Linux numbers:– ~20K cycles to create and reap a process– ~10K cycles (or less) to create and reap a thread
9
멀티 쓰레드 (multithread): 단일쓰레드의 이용 예
“a.txt” “파일을 b.txt” 파일로 복사한다 .
단일 쓰레드 구현
while( NOT eof){
read a line from a.txt into buffer B ;
write a line to b.txt from buffer B ;
}
cp a.txt b.txt
10
멀티 쓰레드 (multithread): 멀티쓰레드의 이용 예
“a.txt” “파일을 b.txt” 파일로 복사한다 .
다중 쓰레드 구현
cp a.txt b.txt
while( NOT eof){ read a line from a.txt into B ; wait till B is empty ;}
while( NOT eof){ wait till buffer B is full ; write a line from B to b.txt ;}
쓰레드 1 쓰레드 2
12
쓰레드 (thread)
쓰레드 (thread) 는 CPU 이용 (utilization) 의 기본 단위 .
쓰레드는 Light Weight Process 라고 부르기도 함 .
– 프로세스는 자신의 프로그램 카운터 , 레지스터 값들 , 주소공간을 가짐 .
– 쓰레드는 자신의 프로그램 카운터 , 레지스터 값들을 가짐 . 자신의 주소공간은“ ”없음
– 다른 쓰레드와 주소공간을 공유함 .
code
data
stack
프로세스주소공간(virtual address space)
Code:int a=3;a++;for(int i=0; i<10; i++) a++;
int a; // automaticregister int a; // registervolatile int a; // memory (cache or ram)
13
멀티 쓰레드 (multithread)
프로세스의 구성– 단일 쓰레드 프로세스– 멀티 쓰레드 프로세스
code data files
registers stack
code data files
regis-ters
regis-ters
regis-ters
stack stack stack
ThreadThread
shared
Carnegie Mellon
14Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Posix Threads (Pthreads) Interface Pthreads: Standard interface for ~60
functions that manipulate threads from C programs Creating and reaping threads
pthread_create() pthread_join()
Determining your thread ID pthread_self()
Terminating threads pthread_cancel() pthread_exit() exit() [terminates all threads] , RET [terminates
current thread] Synchronizing access to shared variables
pthread_mutex_init pthread_mutex_[un]lock
Carnegie Mellon
15Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
void *thread(void *vargp) /* thread routine */{ printf("Hello, world!\n"); return NULL; }
The Pthreads "hello, world" Program
/* * hello.c - Pthreads "hello, world" program */#include "csapp.h"void *thread(void *vargp);
int main(){ pthread_t tid; Pthread_create(&tid, NULL, thread, NULL); Pthread_join(tid, NULL); exit(0); }
Thread attributes (usually NULL)
Thread arguments(void *p)
Return value(void **p)
hello.c
Thread ID
Thread routine
hello.c
Carnegie Mellon
16Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Execution of Threaded “hello, world”
Main thread
Peer thread
return NULL;Main thread waits for peer thread to terminate
exit() Terminates
main thread and any peer threads
call Pthread_create()
call Pthread_join()
Pthread_join() returns
printf()
Peer threadterminates
Pthread_create() returns
17
Thread Creation
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void), void *restrict arg);
Returns: 0 if OK, error number on failure
Tidp: pointer to thread structure
Start_rtn: pointer to function to execute
argument
18
Thread Identification
#include <pthread.h>int pthread_equal(pthread_t tid1, pthread_t tid2);
Returns: nonzero if equal, 0 otherwise
#include <pthread.h>pthread_t pthread_self(void);
Returns: the thread ID of the calling thread
19
Thread Identification
pthread_t
– Linux 2.4.22: unsigned long integer
– Solaris 9: unsigned integer.
– FreeBSD 5.2.1 and Mac OS X 10.3: a pointer to the pthread structure for the pthread_t data, no portable way to print its value.
20
Printing thread IDs
#include "apue.h"#include <pthread.h>pthread_t ntid;
void printids(const char *s){pid_t pid;pthread_t tid;pid = getpid();tid = pthread_self();printf("%s pid %u tid %u (0x%x)\n", s,
(unsigned int)pid, (unsigned int)tid, (unsigned int)tid);}void * thr_fn(void *arg){
printids("new thread: ");return((void *)0);
}int main(void){
int err;err = pthread_create(&ntid, NULL, thr_fn, NULL);if (err != 0)
err_quit("can't create thread: %s\n", strerror(err));printids("main thread:");sleep(1);exit(0);
}
21
Printing thread IDs
Solaris gives us
$ ./a.outmain thread: pid 7225 tid 1 (0x1)new thread: pid 7225 tid 4 (0x4)
FreeBSD gives us
Mac OS X to be similar to FreeBSD; however, address ranges are different
$ ./a.outmain thread: pid 14954 tid 134529024 (0x804c000)new thread: pid 14954 tid 134530048 (0x804c400)
$ ./a.outmain thread: pid 779 tid 2684396012 (0xa000a1ec)new thread: pid 779 tid 25166336 (0x1800200)
22
Printing thread IDs
Linux
$ ./a.out
new thread: pid 6628 tid 1026 (0x402)
main thread: pid 6626 tid 1024 (0x400)
23
Thread Termination
A single thread can exit in three ways, thereby stopping its flow of control, with-
out terminating the entire process.
1. The thread can simply return from the start routine. The return value is the thread's exit code.
2. The thread can be canceled by another thread in the same process.
3. The thread can call pthread_exit.
#include <pthread.h>
void pthread_exit(void *rval_ptr);
24
Thread Termination
The calling thread will block until the specified thread calls pthread_exit, returns
from its start routine, or is canceled.
If the thread simply returned from its start routine, rval_ptr will contain the re-
turn code. If the thread was canceled, the memory location specified by rval_ptr
is set to PTHREAD_CANCELED.
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
Returns: 0 if OK, error number on failure
25
Fetching the thread exit status#include "apue.h"#include <pthread.h>void * thr_fn1(void *arg){
printf("thread 1 returning\n");return((void *)1);
}void * thr_fn2(void *arg){
printf("thread 2 exiting\n");pthread_exit((void *)2);
}int main(void){
int err;pthread_t tid1, tid2;void *tret;err = pthread_create(&tid1, NULL, thr_fn1, NULL);if (err != 0)
err_quit("can't create thread 1: %s\n", strerror(err));err = pthread_create(&tid2, NULL, thr_fn2, NULL);if (err != 0)
err_quit("can't create thread 2: %s\n", strerror(err));err = pthread_join(tid1, &tret);if (err != 0)
err_quit("can't join with thread 1: %s\n", strerror(err));printf("thread 1 exit code %d\n", (int)tret);err = pthread_join(tid2, &tret);if (err != 0)
err_quit("can't join with thread 2: %s\n", strerror(err));printf("thread 2 exit code %d\n", (int)tret);exit(0);
}
26
Fetching the thread exit status
Running the program in Figure 11.3 gives us
$ ./a.outthread 1 returningthread 2 exitingthread 1 exit code 1thread 2 exit code 2
27
Incorrect use of pthread_exit argument
#include "apue.h"#include <pthread.h>
struct foo { int a, b, c, d;};
void printfoo(const char *s, const struct foo *fp){ printf(s); printf(" structure at 0x%x\n“, (unsigned)fp); printf(" foo.a = %d\n", fp->a); printf(" foo.b = %d\n", fp->b); printf(" foo.c = %d\n", fp->c); printf(" foo.d = %d\n", fp->d);}
void * thr_fn1(void *arg){ struct foo foo = {1, 2, 3, 4}; printfoo("thread 1:\n", &foo); pthread_exit((void *)&foo);}
void * thr_fn2(void *arg){ printf("thread 2: ID is %d\n“, pthread_self()); pthread_exit((void *)0);}
28
Incorrect use of pthread_exit argument
int main(void){
int err;
pthread_t tid1, tid2;
struct foo *fp;
err = pthread_create(&tid1, NULL, thr_fn1, NULL);
if (err != 0)
err_quit("can't create thread 1: %s\n", strerror(err));
err = pthread_join(tid1, (void *)&fp);
if (err != 0)
err_quit("can't join with thread 1: %s\n", strerror(err));
sleep(1);
printf("parent starting second thread\n");
err = pthread_create(&tid2, NULL, thr_fn2, NULL);
if (err != 0)
err_quit("can't create thread 2: %s\n", strerror(err));
sleep(1);
printfoo("parent:\n", fp);
exit(0);
}
29
Incorrect use of pthread_exit argument
the problem with using an automatic variable as the argument to
pthread_exit.
When we run this program on Linux, we get
$ ./a.outthread 1:structure at 0x409a2abcfoo.a = 1foo.b = 2foo.c = 3foo.d = 4parent starting second threadthread 2: ID is 32770parent:structure at 0x409a2abcfoo.a = 0foo.b = 32770foo.c = 1075430560foo.d = 1073937284
30
Example – Figure 11.4 Incorrect use of pthread_exit argument
The results on FreeBSD are similar:
$ ./a.outthread 1:structure at 0xbfafefc0foo.a = 1foo.b = 2foo.c = 3foo.d = 4parent starting second threadthread 2: ID is 134534144parent:structure at 0xbfafefc0foo.a = 0foo.b = 134534144foo.c = 3foo.d = 671642590
31
Thread Termination
One thread can request that another in the same process be canceled by call-
ing the pthread_cancel function.
pthread_cancel will cause the thread specified by tid to behave as if it had
called pthread_exit with an argument of PTHREAD_CANCELED.
Note that pthread_cancel doesn‘t wait for the thread to terminate. It merely
makes the request.
#include <pthread.h>
int pthread_cancel(pthread_t tid);
Returns: 0 if OK, error number on failure
32
Thread Termination
A thread can arrange for functions to be called when it exits.: thread cleanup
handlers.
More than one cleanup handler can be established for a thread. The handlers
are recorded in a stack, which means that they are executed in the reverse or-
der from that with which they were registered.
#include <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
33
Thread Termination Cleanup handlers are called when
– Makes a call to pthread_exit
– Responds to a cancellation request
– Makes a call to pthread_cleanup_pop with a nonzero execute argument
– If the execute argument is set to zero, the cleanup function is not called. In either
case, pthread_cleanup_pop removes the cleanup handler established by the last call
to pthread_cleanup_push.
34
Example – Figure 11.5 Thread cleanup handler#include "apue.h"#include <pthread.h>void cleanup(void *arg){ printf("cleanup: %s\n", (char *)arg);}
void * thr_fn1(void *arg){ printf("thread 1 start\n"); pthread_cleanup_push(cleanup, "thread 1 first handler"); pthread_cleanup_push(cleanup, "thread 1 second handler"); printf("thread 1 push complete\n"); if (arg) return((void *)1); pthread_cleanup_pop(0); pthread_cleanup_pop(0); return((void *)1);}
void * thr_fn2(void *arg){ printf("thread 2 start\n"); pthread_cleanup_push(cleanup, "thread 2 first handler"); pthread_cleanup_push(cleanup, "thread 2 second handler"); printf("thread 2 push complete\n"); if (arg) pthread_exit((void *)2); pthread_cleanup_pop(0); pthread_cleanup_pop(0); pthread_exit((void *)2);}
35
Example – Figure 11.5 Thread cleanup handler
int main(void){ int err; pthread_t tid1, tid2; void *tret; err = pthread_create(&tid1, NULL, thr_fn1, (void *)1); if (err != 0) err_quit("can't create thread 1: %s\n", strerror(err)); err = pthread_create(&tid2, NULL, thr_fn2, (void *)1); if (err != 0) err_quit("can't create thread 2: %s\n", strerror(err)); err = pthread_join(tid1, &tret); if (err != 0) err_quit("can't join with thread 1: %s\n", strerror(err)); printf("thread 1 exit code %d\n“, (int)tret); err = pthread_join(tid2, &tret); if (err != 0) err_quit("can't join with thread 2: %s\n", strerror(err)); printf("thread 2 exit code %d\n“, (int)tret); exit(0);}
36
Thread cleanup handler
Note that although we never intend to pass a nonzero argument to the
thread start-up routines, we still need to match calls to
pthread_cleanup_pop with the calls to pthread_cleanup_push; other-
wise, the program might not compile.
Running the program in Figure 11.5 gives us
$ ./a.outthread 1 startthread 1 push completethread 2 startthread 2 push completecleanup: thread 2 second handlercleanup: thread 2 first handlerthread 1 exit code 1thread 2 exit code 2
37
Thread Termination
Process
Thread Description
fork pthread_create create a new flow of con-trol
exit pthread_exit exit from an existing flow of control
wait-pid
pthread_join get exit status from flow of control
atexit pthread_cleanup_push
register function to be called at exit from flow of control
get-pid
pthread_self get ID for flow of control
abort pthread_cancel request abnormal termi-nation of flow of control
Carnegie Mellon
38Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Pros and Cons of Thread-Based Designs
+ Easy to share data structures between threads e.g., logging information, file cache
+ Threads are more efficient than processes
– Unintentional sharing can introduce subtle and hard-to-reproduce errors! The ease with which data can be shared is both the
greatest strength and the greatest weakness of threads Hard to know which data shared & which private Hard to detect by testing
Probability of bad race outcome very low But nonzero!
Carnegie Mellon
39Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Shared Variables in Threaded C Programs
Question: Which variables in a threaded C program are shared? The answer is not as simple as “global variables are shared”
and “stack variables are private”
Def: A variable x is shared if and only if multiple threads reference some instance of x.
Requires answers to the following questions: What is the memory model for threads? How are instances of variables mapped to memory? How many threads might reference each of these instances?
Carnegie Mellon
40Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Threads Memory Model Conceptual model:
Multiple threads run within the context of a single process Each thread has its own separate thread context
Thread ID, stack, stack pointer, PC, condition codes, and GP registers
All threads share the remaining process context Code, data, heap, and shared library segments of the process virtual
address space Open files and installed handlers
Operationally, this model is not strictly enforced: Register values are truly separate and protected, but… Any thread can read and write the stack of any other thread
The mismatch between the conceptual and operation model is a source of confusion and errors
Carnegie Mellon
41Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Example Program to Illustrate Sharing
char **ptr; /* global var */
int main(){ long i; pthread_t tid; char *msgs[2] = { "Hello from foo", "Hello from bar" };
ptr = msgs; for (i = 0; i < 2; i++) Pthread_create(&tid, NULL, thread, (void *)i); Pthread_exit(NULL);}
void *thread(void *vargp){ long myid = (long)vargp; static int cnt = 0;
printf("[%ld]: %s (cnt=%d)\n", myid, ptr[myid], ++cnt); return NULL;}
Peer threads reference main thread’s stackindirectly through global ptr variable
sharing.c
Carnegie Mellon
42Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Mapping Variable Instances to Memory
Global variables Def: Variable declared outside of a function Virtual memory contains exactly one instance of any
global variable
Local variables Def: Variable declared inside function without static attribute Each thread stack contains one instance of each local
variable
Local static variables Def: Variable declared inside function with the static attribute Virtual memory contains exactly one instance of any local
static variable.
Carnegie Mellon
43Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
char **ptr; /* global var */
int main(){ long i; pthread_t tid; char *msgs[2] = { "Hello from foo", "Hello from bar" };
ptr = msgs; for (i = 0; i < 2; i++) Pthread_create(&tid, NULL, thread, (void *)i); Pthread_exit(NULL);}
void *thread(void *vargp){ long myid = (long)vargp; static int cnt = 0;
printf("[%ld]: %s (cnt=%d)\n", myid, ptr[myid], ++cnt); return NULL;}
Mapping Variable Instances to Memory
Global var: 1 instance (ptr [data])
Local static var: 1 instance (cnt [data])
Local vars: 1 instance (i.m, msgs.m)
Local var: 2 instances ( myid.p0 [peer thread 0’s stack], myid.p1 [peer thread 1’s stack])
sharing.c
Carnegie Mellon
44Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Shared Variable Analysis Which variables are shared?
Answer: A variable x is shared iff multiple threads reference at least one instance of x. Thus:
ptr, cnt, and msgs are shared i and myid are not shared
Variable Referenced by Referenced by Referenced byinstance main thread? peer thread 0? peer thread 1?
ptrcnti.mmsgs.mmyid.p0myid.p1
yes yes yesno yes yesyes no noyes yes yesno yes nono no yes
Carnegie Mellon
45Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Synchronizing Threads Shared variables are handy...
…but introduce the possibility of nasty synchronization errors.
Carnegie Mellon
46Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
badcnt.c: Improper Synchronization
/* Global shared variable */volatile long cnt = 0; /* Counter */
int main(int argc, char **argv){ long niters; pthread_t tid1, tid2;
niters = atoi(argv[1]); Pthread_create(&tid1, NULL, thread, &niters); Pthread_create(&tid2, NULL, thread, &niters); Pthread_join(tid1, NULL); Pthread_join(tid2, NULL);
/* Check result */ if (cnt != (2 * niters)) printf("BOOM! cnt=%ld\n", cnt); else printf("OK cnt=%ld\n", cnt); exit(0);}
/* Thread routine */ void *thread(void *vargp) { long i, niters = *((long *)vargp); for (i = 0; i < niters; i++) cnt++; return NULL; }
linux> ./badcnt 10000OK cnt=20000linux> ./badcnt 10000BOOM! cnt=13051linux>
cnt should equal 20,000.
What went wrong?badcnt.c
Carnegie Mellon
47Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Assembly Code for Counter Loop
for (i = 0; i < niters; i++) cnt++;
C code for counter loop in thread i
movq (%rdi), %rcx testq %rcx,%rcx jle .L2 movl $0, %eax.L3: movq cnt(%rip),%rdx addq $1, %rdx movq %rdx, cnt(%rip) addq $1, %rax cmpq %rcx, %rax jne .L3.L2:
Hi : Head
Ti : Tail
Li : Load cntUi : Update cntSi : Store cnt
Asm code for thread i
Carnegie Mellon
48Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Concurrent Execution Key idea: In general, any sequentially
consistent interleaving is possible, but some give an unexpected result! Ii denotes that thread i executes instruction I %rdxi is the content of %rdx in thread i’s context
H1
L1
U1
S1
H2
L2
U2
S2
T2
T1
1111222221
-011-----1
0001111222
i (thread)instri cnt%rdx1
OK
-----1222-
%rdx2
Thread 1 critical sectionThread 2 critical section
Carnegie Mellon
49Bryant and O’Hallaron, Computer Systems: A Programmer’s Perspective, Third Edition
Concurrent Execution (cont) Incorrect ordering: two threads increment the
counter, but the result is 1 instead of 2
H1
L1
U1
H2
L2
S1
T1
U2
S2
T2
1112211222
-01--11---
0000011111
i (thread)instri cnt%rdx1
----0--111
%rdx2
Oops!
병행성 문제 병행성 (concurrency) 문제
두 개 이상의 스레드가 어떤 객체에 있는 하나의 데이터에 접근하게 되는 경우
서로 다른 두 스택에서 실행되는 메소드가 객체에 있는 동일한 객체에 대한 게터 또는 세터 메소드를 호출하게 되는 경우
스레드는 자신이 잠시 중단된 적이 있다는 것을 기억할 수가 없음
라이언과 모니카 이야기 절대 마이너스 통장이 되지 않도록 하자는 약속을 했음
라이언이 50 달러를 빼기 위해 은행 잔고 확인 , 100 달러가
남아있음을 확인했는데 잠이 들었음
라이언이 잠든 상태에서 모니카가 100 달러가 필요해서 잔
고를 확인하고 바로 돈을 인출
잠에서 깬 라이언은 통장 잔고가 여전히 100 달러라고 생각
하고 50 달러 인출
결국 통장 잔고가 마이너스가 됨
synchronized 키워드 (Java 언어 )
어떤 메소드를 한 번에 한 스레드만 접근할 수 있도록 만들 때는 synchronized 라는 키워드를 씀
synchronized 키워드는 그 동기화된 코드에 접근하려면 스레드가 열쇠를 가지고 있어야 한다는 것을 의미함
private synchronized void makeWithdrawal(int amount) {……}
54
Thread Synchronization Thread A reads the variable and then writes a new value to it, but the write op-
eration takes two memory cycles. If thread B reads the same variable between
the two write cycles, it will see an inconsistent value.
read
write 1
write 2
read
Thread A Thread B
time
read
read
read
write 1
write 2
Thread A
Thread B
55
Thread Synchronization - Mutexes
A mutex is basically a lock that we set (lock) before accessing a shared re-
source and release (unlock) when we're done. While it is set, any other thread
that tries to set it will block until we release it.
If more than one thread is blocked when we unlock the mutex, then all threads
blocked on the lock will be made runnable, and the first one to run will be able
to set the lock.
The others will see that the mutex is still locked and go back to waiting for it to
become available again. In this way, only one thread will proceed at a time.
56
Thread Synchronization - Mutexes
A mutex variable is represented by the pthread_mutex_t data type.
Before we can use a mutex variable, we must first initialize it by either setting it to
the constant PTHREAD_MUTEX_INITIALIZER (for statically-allocated mutexes
only) or calling pthread_mutex_init.
If we allocate the mutex dynamically (by calling malloc, for example), then we need
to call pthread_mutex_destroy before freeing the memory.
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
Both return: 0 if OK, error number on failure
57
Thread Synchronization - Mutexes
To lock a mutex, we call pthread_mutex_lock. If the mutex is already locked,
the calling thread will block until the mutex is unlocked. To unlock a mutex, we
call pthread_mutex_unlock.
Trylock: If the mutex is unlocked at the time pthread_mutex_trylock is called,
then pthread_mutex_trylock will lock the mutex without blocking and return 0.
Otherwise, pthread_mutex_trylock will fail, returning EBUSY without locking
the mutex.
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
All return: 0 if OK, error number on failure
58
Using a mutex to protect a data structure
#include <stdlib.h>#include <pthread.h>struct foo { int f_count; pthread_mutex_t f_lock; /* ... more stuff here ... */};struct foo * foo_alloc(void) /* allocate the object */{ struct foo *fp; if ((fp = malloc(sizeof(struct foo))) != NULL) { fp->f_count = 1; if (pthread_mutex_init(&fp->f_lock, NULL) != 0) { free(fp); return(NULL); } /* ... continue initialization ... */ } return(fp);}
59
Using a mutex to protect a data structure
void foo_hold(struct foo *fp) /* add a reference to the object */{ pthread_mutex_lock(&fp->f_lock); fp->f_count++; pthread_mutex_unlock(&fp->f_lock);}
void foo_rele(struct foo *fp) /* release a reference to the object */{ pthread_mutex_lock(&fp->f_lock); if (--fp->f_count == 0) { /* last reference */ pthread_mutex_unlock(&fp->f_lock); pthread_mutex_destroy(&fp->f_lock); free(fp); } else { pthread_mutex_unlock(&fp->f_lock); }}
60
Using a mutex to protect a data structure
When more than one thread needs to access a dynamically-allocated object, we
can embed a reference count in the object to ensure that we don't free its memory
before all threads are done using it.
We lock the mutex before incrementing the reference count, decrementing the ref-
erence count, and checking whether the reference count reaches zero.
Before using the object, threads are expected to add a reference count to it. When
they are done, they must release the reference. When the last reference is re-
leased, the object's memory is freed.