Operating Systems Practical Session 4 Threads 1 Threads

  • Slides: 39
Download presentation
Operating Systems Practical Session 4 Threads 1

Operating Systems Practical Session 4 Threads 1

Threads • Executed within a process. • Allow multiple independent executions under the same

Threads • Executed within a process. • Allow multiple independent executions under the same process (container). • Possible states: running, ready, blocked, terminated. • In most of today’s operating systems, a process is created with at least one thread but may have more than one thread (multithreading). 2

Threads - Advantages • Share open files, data structures, global variables, child processes, etc.

Threads - Advantages • Share open files, data structures, global variables, child processes, etc. • Peer threads can communicate without using System communicate calls. • Threads are faster to create/terminate/switch than faster processes (have no resources attached). • Parallelism which improves overall performance: Parallelism – A single core CPU and a substantial amount of computing and I/O – Multiple cores 3

Threads - Disadvantages • Share open files, data structures, global variables, Share child processes,

Threads - Disadvantages • Share open files, data structures, global variables, Share child processes, etc. • No protection between threads – one can protection read/write/wipe out/corrupt the other’s data. • Sending some signals (such as SIGSTOP) to a process affects all threads running within it. 4

Threads vs. Processes (“classic” approach – Linux’s clone results in some ambiguity) Processes Threads

Threads vs. Processes (“classic” approach – Linux’s clone results in some ambiguity) Processes Threads unique open I/O shared open I/O unique signal table shared signal table unique stack unique PC unique registers unique state heavy context switch light context switch Notice, signal handlers must be shared among all threads of a multithreaded application. 5

Threads - motivation Dispatcher thread: while (TRUE) { get_next_request(&buf); handoff_work(&buf); } Dispatcher Worker thread:

Threads - motivation Dispatcher thread: while (TRUE) { get_next_request(&buf); handoff_work(&buf); } Dispatcher Worker thread: Workers while (TRUE) { Web page cache wait_for_work(&buf); look_for_page_in_cache(&buf, &page); if (page_not_in_cache(&page)) Webpage read_page_from_disk(&buf, &page); request return page(&page); } Why are threads better in this case? Such communication requires a shared memory 6 Example from “Modern Operating Systems”, 2 nd Edition, pg. 88

Threads – some known Issues • Does the fork() command duplicate just the calling

Threads – some known Issues • Does the fork() command duplicate just the calling thread or all threads of the process? – POSIX defines that only the calling thread is replicated in the child process. – Solaris 10 defines fork 1() and forkall() which attempt to better define the relation between fork and threads, however, this is not POSIX compliant. – Many issues arise from using fork in a multithreaded code. Ø Unless calling exec immediately after fork, try to avoid it! • Does the exec() command replace the entire process? – The entire process is replaced including all its threads. 7

User-level and Kernel-level Threads Library User space Kernel space P P (a) Pure user-level

User-level and Kernel-level Threads Library User space Kernel space P P (a) Pure user-level (b) Pure kernel-level 8

User-level and Kernel-level Threads Library P User space Kernel space P (c) Combined Source:

User-level and Kernel-level Threads Library P User space Kernel space P (c) Combined Source: Stallings, Operating Systems: Internals and design principles 7 th ed. 9

User-level threads • The kernel sees just the main thread of the process (all

User-level threads • The kernel sees just the main thread of the process (all other threads that run within the process’ context are “invisible” to the OS). • The user application – not the kernel – is responsible for scheduling CPU time for its internal threads within the running time scheduled for it by the kernel. 10

User-level threads (cont’d) • User-level threads implement in user-level libraries, rather than via systems

User-level threads (cont’d) • User-level threads implement in user-level libraries, rather than via systems calls, so thread switching does not need to call operating system and to cause interrupt to the kernel. • The kernel’s inability to distinguish between user level threads makes it difficult to design preemptive scheduling. • If a thread makes a blocking system call, the entire process is blocked. • Will only utilize a single CPU. 11

Kernel-level threads • All threads are visible to the kernel. • The kernel manages

Kernel-level threads • All threads are visible to the kernel. • The kernel manages the threads. • The kernel schedules each thread within the time-slice of each process. • The user cannot define the scheduling policy. • Context switching is slower for kernel threads than for user-level threads. • Because the kernel is aware of the threads, in multiple CPU machines, each CPU can run a different thread of the same process, at the same time. 12

User-level vs. kernel-level threads User-level threads Kernel-level threads Threads Invisible to the kernel Visible

User-level vs. kernel-level threads User-level threads Kernel-level threads Threads Invisible to the kernel Visible to the kernel Scheduling policy User defined Kernel defined Thread switching Non-preemptive* Preemptive Context switch Faster, done by the runtime Slower, done by the kernel Blocking calls Block the whole process Block the single thread Thread table Held by the process Held by the kernel 13

A tech. note on POSIX threads • When the first Unix and POSIX functions

A tech. note on POSIX threads • When the first Unix and POSIX functions were designed, it was assumed that there will be a single thread of execution. • Hence, the need for reentrant functions. • Reentrant functions are safe to call before a previous call has finished (usually using only local variables or locking mechanisms) • While this is supported by many standard functions, the compiler must be aware of the need for re-entrant functions: – gcc –D_REENTRANT –lpthread … 14

Threads in POSIX (pthreads) int pthread_create( pthread_t* thread, pthread_attr_t* attr, void* (*start_func)(void*) , void*

Threads in POSIX (pthreads) int pthread_create( pthread_t* thread, pthread_attr_t* attr, void* (*start_func)(void*) , void* arg) Creates a new thread of control that executes concurrently with the calling thread. The identifier of the newly created thread is stored in the location pointed by the thread argument, and a 0 is returned. attr specifies thread attributes that will be applied to the new thread (e. g. detached, scheduling-policy). Can be NULL (default attributes). start_func is pointer to the function the thread will start executing; arg contains parameter to the funtion func. Thread type is platform-specific pthread_t pthread_self() return this thread’s identifier. 15

Threads in POSIX (pthreads) – cont’d int pthread_join( pthread_t th, void** thread_return ) Suspends

Threads in POSIX (pthreads) – cont’d int pthread_join( pthread_t th, void** thread_return ) Suspends the execution of the calling thread until the thread identified by th terminates. th is the identifier of the thread that needs to be waited for. At most one thread can wait for the termination of a given thread. The return value of th is stored in the location pointed by thread_return void pthread_exit( void* ret_val ) Terminates the execution of the calling thread. Doesn’t terminate the whole process if called from the main function. If ret_val is not null, then ret_val is saved, and its value is given to the thread who performed join on this thread; that is, it will be written to the thread_return parameter in the pthread_join call. 16

Hello World! #include <pthread. h> #include <stdio. h> When compiling a multi-threaded app: gcc

Hello World! #include <pthread. h> #include <stdio. h> When compiling a multi-threaded app: gcc –D_REENTRANT –o myprog. c –lpthread void *printme() { printf("Hello World!n"); return NULL; } void main() { pthread_t tcb; void *status; if (pthread_create(&tcb, NULL, printme, NULL) != 0) { perror("pthread_create"); exit(1); } if (pthread_join(tcb, &status) != 0) { perror("pthread_join"); exit(1); What can happen if we } remove the join part? } 17

Example A – Version 1 void *printme(void *id) { int *i; i = (int

Example A – Version 1 void *printme(void *id) { int *i; i = (int *)id; printf("Hi. I'm thread %dn", *i); return NULL; } void main() { int i, vals[4]; pthread_t tids[4]; void *retval; for (i = 0; i < 4; i++) { vals[i] = i; pthread_create(tids+i, NULL, printme, vals+i); } for (i = 0; i < 4; i++) { printf("Trying to join with tid%dn", i); pthread_join(tids[i], &retval); printf("Joined with tid%dn", i); } } 18

Example A – Version 1 possible output Trying to join with tid 0 Hi.

Example A – Version 1 possible output Trying to join with tid 0 Hi. I'm thread 1 Hi. I'm thread 2 Hi. I'm thread 3 Joined with tid 0 Trying to join with tid 1 Joined with tid 1 Trying to join with tid 2 Joined with tid 2 Trying to join with tid 3 Joined with tid 3 19

Example A – Version 2 void *printme(void *id) { int *i; i = (int

Example A – Version 2 void *printme(void *id) { int *i; i = (int *)id; printf("Hi. I'm thread %dn", *i); pthread_exit(NULL); } void main() { int i, vals[4]; pthread_t tids[4]; void *retval; for (i = 0; i < 4; i++) { vals[i] = i; pthread_create(tids+i, NULL, printme, vals+i); } for (i = 0; i < 4; i++) { printf("Trying to join with tid%dn", i); pthread_join(tids[i], &retval); printf("Joined with tid%dn", i); } pthread_exit(NULL); } 20

Example A – Version 2 possible output Trying to join with tid 0 Hi.

Example A – Version 2 possible output Trying to join with tid 0 Hi. I'm thread 1 Hi. I'm thread 2 Hi. I'm thread 3 Joined with tid 0 Trying to join with tid 1 Joined with tid 1 Trying to join with tid 2 Joined with tid 2 Trying to join with tid 3 Joined with tid 3 21

Example A – Version 3 void *printme(void *id) { int *i; i = (int

Example A – Version 3 void *printme(void *id) { int *i; i = (int *)id; printf("Hi. I'm thread %dn", *i); pthread_exit(NULL); } void main() { int i, vals[4]; pthread_t tids[4]; void *retval; for (i = 0; i < 4; i++) { vals[i] = i; pthread_create(tids+i, NULL, printme, vals+i); } pthread_exit(NULL); for (i = 0; i < 4; i++) { printf("Trying to join with tid%dn", i); pthread_join(tids[i], &retval); printf("Joined with tid%dn", i); } } 22

Example A – Version 3 output Hi. I'm thread 0 Hi. I'm thread 1

Example A – Version 3 output Hi. I'm thread 0 Hi. I'm thread 1 Hi. I'm thread 2 Hi. I'm thread 3 If the main thread calls pthread_exit(), the process will continue executing until the last thread terminates or the whole process is terminated 23

Example A – Version 4 void *printme(void *id) { int *i = (int *)id;

Example A – Version 4 void *printme(void *id) { int *i = (int *)id; sleep(5); printf("Hi. I'm thread %dn", *i); pthread_exit(NULL); } int main() { int i, vals[4]; pthread_t tids[4]; void *retval; for (i = 0; i < 4; i++) { vals[i] = i; pthread_create(tids+i, NULL, printme, vals+i); } return 0; } 24

Example A – Version 4 possible output No Output! 25

Example A – Version 4 possible output No Output! 25

Example A – Version 5 void *printme(void *id) { int *i; i = (int

Example A – Version 5 void *printme(void *id) { int *i; i = (int *)id; printf("Hi. I'm thread %dn", *i); exit(0); } main() { int i, vals[4]; pthread_t tids[4]; void *retval; for (i = 0; i < 4; i++) { vals[i] = i; pthread_create(tids+i, NULL, printme, vals+i); } for (i = 0; i < 4; i++) { printf("Trying to join with tid%dn", i); pthread_join(tids[i], &retval); printf("Joined with tid%dn", i); } pthread_exit(NULL); } 26

Example A – Version 5 possible output Trying to join with tid 0 Hi.

Example A – Version 5 possible output Trying to join with tid 0 Hi. I'm thread 0 27

Midterm – 2006 (cont’d) : ( פתרון )א 1 5 4 2 6 3

Midterm – 2006 (cont’d) : ( פתרון )א 1 5 4 2 6 3 29

Thread-specific data • Programs often need global or static variables that have different values

Thread-specific data • Programs often need global or static variables that have different values in different threads: Thread-specific data (TSD). data • Each thread possesses a private memory block, the TSD area. • This area is indexed by TSD keys (Map). • TSD keys are common to all threads, but the value associated with a given TSD key can be different in each thread. • Defined in POSIX. 33

Thread-specific data (cont’d) • Question: Why can’t we achieve this by using regular variables?

Thread-specific data (cont’d) • Question: Why can’t we achieve this by using regular variables? • Because threads share one memory space. • Usage examples: – Separate log for each thread. 34

Thread-specific data (cont’d) int pthread_key_create(pthread_key_t* key, void (*destr_func)(void*)) Allocates a new TSD key. Return

Thread-specific data (cont’d) int pthread_key_create(pthread_key_t* key, void (*destr_func)(void*)) Allocates a new TSD key. Return 0 on success and a non-zero error code on failure. key the key is stored in the location pointed to by key. destr_func if not NULL, specifies a destructor function associated with the key. When a thread terminates via pthread_exit, destr_func is called with arguments – the value associated with the key in that thread. The order in which destructor functions are called at thread termination time is unspecified. int pthread_key_delete(pthread_key_t key) Deallocates a TSD key. Return 0 on success and a non-zero error code on failure. It does not check whether non-NULL values are associated with that key in the currently executing threads, nor call the destructor function associated with the key the key of the value to delete. 35

Thread-specific data (cont’d) int pthread_setspecific(pthread_key_t key, const void* pointer) Changes the value associated with

Thread-specific data (cont’d) int pthread_setspecific(pthread_key_t key, const void* pointer) Changes the value associated with key in the calling thread, storing the given pointer instead. void* pthread_getspecific(pthread_key_t key) Returns the value currently associated with key in the calling thread, or NULL on error. 36

TSD Usage example Suppose, for instance, that your application divides a task among multiple

TSD Usage example Suppose, for instance, that your application divides a task among multiple threads. For audit purposes, each thread is to have a separate log file, in which progress messages for that thread's tasks are recorded. The thread-specific data area is a convenient place to store the file pointer for the log file for each individual thread. 37

#include <malloc. h> #include <pthread. h> #include <stdio. h> // The key used to

#include <malloc. h> #include <pthread. h> #include <stdio. h> // The key used to associate a log file pointer with each thread. static pthread_key_t thread_log_key; // Write MESSAGE to the log file for the current thread. void write_to_thread_log(const char* message) { FILE* thread_log = (FILE*)pthread_getspecific (thread_log_key); fprintf(thread_log, "%sn", message); } // Close the log file pointer THREAD_LOG. void close_thread_log (void* thread_log) { fclose((FILE*) thread_log); } 38

void* thread_function (void* args) { char thread_log_filename[20]; FILE* thread_log; sprintf(thread_log_filename, "thread%d. log", (int) pthread_self());

void* thread_function (void* args) { char thread_log_filename[20]; FILE* thread_log; sprintf(thread_log_filename, "thread%d. log", (int) pthread_self()); thread_log = fopen (thread_log_filename, "w"); pthread_setspecific (thread_log_key, thread_log); write_to_thread_log ("Thread starting. "); /* Do work here. . . */ return NULL; } int main () { int i; pthread_t threads[5]; pthread_key_create (&thread_log_key, close_thread_log); for (i = 0; i < 5; ++i) pthread_create (&(threads[i]), NULL, thread_function, NULL); for (i = 0; i < 5; ++i) pthread_join (threads[i], NULL); return 0; } 39