\documentclass{ictlab} \RCS $Revision: 1.6 $ \usepackage{alltt,key,color,answer2,float,afterpage,biganswerbox,xr} \externaldocument[proc-]{../../lectures/processes/processes-print-3} \usepackage[nolineno,noindent]{lgrind} %\usepackage[leftnum,lineno5,noindent]{lgrind} \ifx\pdftexversion\undefined \else \usepackage[pdfpagemode=None,pdfauthor={Nick Urbanik}]{hyperref} \fi \renewcommand*{\subject}{Operating Systems and Systems Integration} \newcommand*{\labTitle}{Workshop on POSIX Threads and the Problem of Deadlock} \definecolor{light-blue}{rgb}{0.4,0.4,1} \newcommand*{\gl}[1]{\textcolor{light-blue}{#1}} % good link \newcommand*{\ex}[1]{\textcolor{green}{#1}} % executable file \newcommand*{\bl}[1]{\colorbox{red}{\textcolor{white}{\textbf{#1}}}} % bad link \renewcommand{\floatpagefraction}{0.75} % default is .5, to increase % density. \renewcommand*{\bottomfraction}{0.6} % default is 0.3 \renewcommand*{\topfraction}{0.85} % default is 0.7 \renewcommand*{\textfraction}{0.1} % default is 0.2 \setlength{\extrarowheight}{1pt} \floatstyle{ruled} \floatname{program}{Program} \newfloat{program}{tbh}{lop} \providecommand*{\SMP}{\acro{SMP}\xspace}% \begin{document} \section{Background} \label{sec:background} \begin{itemize*} \item \POSIX is a standard for \UNIX \item Linux implements \POSIX threads \item On Red Hat 8.x, documentation is at \begin{alltt} $ \textbf{info '(libc) POSIX Threads'} \end{alltt}%$ \begin{itemize*} \item or in Emacs, \texttt{C-H m libc} then middle-click on \texttt{POSIX threads} \end{itemize*} \item Provides: \begin{itemize*} \item \emph{semaphores}, \item \emph{mutex}es and \item \emph{condition variables} \end{itemize*} for locking (synchronisation) \end{itemize*} \section{Generic Procedure for Compiling POSIX Threads Applications} \label{sec:generic-instructions} \begin{enumerate} \item You need to use the \texttt{libpthread} library \begin{itemize*} \item Specify this with the option \texttt{-lpthread} \end{itemize*} \item Need to tell the other libraries that they should be \emph{reentrant} (or ``\emph{thread safe}'') \begin{itemize*} \item This means that the library uses no static variables that may be overwritten by another thread \item Specify this with the option \texttt{-D\_REENTRANT} \end{itemize*} \item So, to compile the program \meta{program}.c, do: \begin{alltt}\small $ \textbf{gcc -D_REENTRANT -lpthread -o \meta{program} \meta{program}.c} \end{alltt}%$ \end{enumerate} \section{Procedure} \label{sec:procedure} \begin{program}[tbh] %\smallskip% %\begin{verbatim} \vspace*{-2ex} %[ #include #include #include #include #define NUM_THREADS 5 void *print_hello( void *threadid ) { printf( "\n%d: Hello World!\n", ( int ) threadid ); pthread_exit( NULL ); } int main() { static pthread_t threads[ NUM_THREADS ]; int rc, t; for ( t = 0; t < NUM_THREADS; t++ ) { printf( "Creating thread %d\n", t ); rc = pthread_create( &threads[ t ], NULL, print_hello, ( void * ) t ); if ( rc ) { printf( "ERROR; pthread_create() returned %d\n", rc ); printf( "Error string: \"%s\"\n", strerror( rc ) ); exit( -1 ); } } pthread_exit( NULL ); } %] \vspace*{-3ex} %\end{verbatim} \caption{A simple program \texttt{hello.c} that is the program \texttt{hello.c} given in the lecture.} \label{prg:hello} \end{program} \newlength{\wid} \setlength{\wid}{10\linewidth/40} \newlength{\topspace} \newlength{\bottomspace} \setlength{\topspace}{-5.5ex} \setlength{\bottomspace}{-3.5ex} \begin{program} \begin{tabular}{p{\wid}|p{\wid}|p{\wid}|p{\wid}} \emph{deadlock} & \emph{no mutual exclusion} & \emph{no hold \& wait} & \emph{no circular wait}\\ %\hline \vspace*{\topspace}% %[ Thread a() { lock mutex1; give up CPU; lock mutex2; unlock mutex1; unlock mutex2; } Thread b() { lock mutex2; give up CPU; lock mutex1; unlock mutex2; unlock mutex1; } %] \vspace*{\bottomspace} &% % no mutual exclusion \begin{solution}% \vspace*{\topspace}% %[ /* No locking No mutexes */ Thread a() { use resource1; use resource2; } Thread b() { use resource2; use resource1; } %] \vspace*{\bottomspace}% \end{solution}% &% % no hold \& wait \begin{solution}% \vspace*{\topspace} %[ Thread a() { /* no H & W */ lock mutex1; give up CPU; unlock mutex1; lock mutex2; unlock mutex2; } Thread b() { lock mutex2; give up CPU; lock mutex1; unlock mutex2; unlock mutex1; } %] \vspace*{\bottomspace}% \end{solution}% & % no circular wait \begin{solution}% \vspace*{\topspace}% %[ Thread a() { lock mutex1; give up CPU; lock mutex2; unlock mutex1; unlock mutex2; } Thread b() { /* same order */ lock mutex1; give up CPU; lock mutex2; unlock mutex1; unlock mutex2; } %] \vspace*{\bottomspace}% \end{solution}% \end{tabular} %\end{verbatim} \caption{Pseudocode for the two threads.} \label{prg:deadlock-pseudocode} \end{program} \begin{program}[tbh] \vspace*{-2ex} %[ #include #include #include "errors.h" /* Initialize 2 mutexes. */ pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; void *locking_thread_a( void *arg ) { int status; printf( "locking_thread_a starting\n" ); status = pthread_mutex_lock( &mutex1 ); if ( status != 0 ) err_abort( status, "lock 1" ); printf( "a has lock 1\n" ); sched_yield(); printf( "a now trying to get lock 2\n" ); status = pthread_mutex_lock( &mutex2 ); printf( "a has lock 2\n" ); if ( status != 0 ) err_abort( status, "lock 2" ); pthread_mutex_unlock( &mutex1 ); pthread_mutex_unlock( &mutex2 ); printf( "locking_thread_a finishing\n" ); pthread_exit( NULL ); } void *locking_thread_b( void *arg ) { int status; printf( "locking_thread_b starting\n" ); status = pthread_mutex_lock( &mutex2 ); if ( status != 0 ) err_abort( status, "lock 2" ); printf( "b has lock 2\n" ); sched_yield(); printf( "b now trying to get lock 1\n" ); status = pthread_mutex_lock( &mutex1 ); if ( status != 0 ) err_abort( status, "lock 1" ); printf( "b has lock 1\n" ); pthread_mutex_unlock( &mutex2 ); pthread_mutex_unlock( &mutex1 ); printf( "locking_thread_b finishing\n" ); pthread_exit( NULL ); } %] \vspace*{\bottomspace} %\end{verbatim} \caption{The first part of the program \texttt{deadlock.c} that is unable to run to completion.} \label{prg:deadlock1} \end{program} \begin{enumerate} \item Download the source code for these programs from the subject web site: \texttt{hello.c}, \texttt{deadlock.c} and \texttt{error.h}. \end{enumerate} \subsection{An Introduction to Posix Threads} \label{sec:intro-to-posix-threads} \begin{enumerate} \item Compile and run program~\vref{prg:hello}, using the generic instructions given in section~\vref{sec:generic-instructions}. \item Refer to the lecture notes on the topic \emph{Processes and Threads} and read the few slides starting at slide~\pageref{proc-sld:pthread_create}. Read about the four parameters that are passed to \texttt{pthread\_create()}. Note that there is a detailed manual page for every single library function used here. For example, you can see \texttt{man pthread\_create}, and \texttt{man strerror}, \texttt{man 3 exit}, \texttt{man 3 printf}, and so on. \item Modify \texttt{hello.c}, increasing the number of threads until you find the maximum number of threads that your system can support. \item Download the program file \texttt{hello-with-logs.c}, which makes each thread do \CPU intensive calculations. Observe the output of \texttt{vmstat~1} and also, run \texttt{top} in another window. Watch the load average.\footnote{\emph{Load Average} is the average number of processes that are in the \emph{ready-to-run} state. In other words, the number of additional processes that would be running if each had a \CPU.} \end{enumerate} \subsection{Deadlock} \label{sec:deadlock} \begin{enumerate} \item Compile and execute the program \texttt{deadlock.c} shown in program~\vref{prg:deadlock1} and program~\ref{prg:deadlock-main-function}. Observe what happens when you run it. \item Read about \emph{mutexes} in slide~\pageref{proc-sld:mutex} in the lecture notes on the topic \emph{Processes and Threads}\e. Also read slides~\pageref{proc-sld:mutex-example-1}--\pageref{proc-sld:mutex-example-3} showing example code using a mutex. \item Notice that most of the code of program~\vref{prg:deadlock1} is \texttt{printf()} statements, error checking and such. The code for the two threads can be expressed quite simply, as shown in program~\vref{prg:deadlock-pseudocode}. Examine the pseudocode in program~\ref{prg:deadlock-pseudocode} and compare it with the C program~\ref{prg:deadlock1}, and see how the pseudocode is a summary of the C program. \item The standard \POSIX library function \texttt{sched\_yield()} allows a thread or process to voluntarily give up the \CPU, so that the scheduler will move it to the end of the queue for its own priority, and allow another thread or process to run. See \texttt{man sched\_yield}. \item Note that there are four conditions required for deadlock; if you remove \emph{any} one of these, then deadlock will not happen. They are: \begin{itemize} \item Mutual exclusion \begin{itemize*} \item where only one process can use a resource at one time \end{itemize*} \item Hold and Wait \begin{itemize*} \item Processes holding resources given earlier can request new resources \end{itemize*} \item No Preemption \begin{itemize*} \item Resources given to a process or thread cannot be taken away forcibly by \OS or anything other than that process or thread \end{itemize*} \item Circular Wait \begin{itemize*} \item Each process is waiting for a resource held by another \end{itemize*} \end{itemize} \newlength{\questionboxheight} \setlength{\questionboxheight}{2.2cm} \item \label{que:mutual-exclusion}Describe here how this program satisfies the deadlock requirement of \emph{mutual exclusion} \begin{biganswerbox}[\questionboxheight]% \begin{solution}% The program uses mutexes, which by their very nature, enforce \textbf{\textit{mut}}ual \textbf{\textit{ex}}clusion. If one thread locks a mutex, no other thread can lock it until the first thread unlocks it. \end{solution} \end{biganswerbox} \item \label{que:hold-and-wait}Describe here how this program satisfies the deadlock requirement of \emph{hold and wait} \begin{biganswerbox}[\questionboxheight]% \begin{solution}% \texttt{locking\_thread\_a()} locks mutex 1, then while still holding it, locks mutex 2. \texttt{locking\_thread\_b()} locks mutex 2, then while still holding it, locks mutex 1. This is hold and wait. \end{solution} \end{biganswerbox} \item \label{que:no-preemption}Describe here how this program satisfies the deadlock requirement of \emph{no preemption} \begin{biganswerbox}[\questionboxheight]% \begin{solution}% A thread cannot take a mutex away from another thread by force, and the code is not written so that one thread will give up the lock voluntarily to the other, so there is no preemption. \end{solution} \end{biganswerbox} \item \label{que:circular-wait}Describe here how this program satisfies the deadlock requirement of \emph{circular wait} \begin{biganswerbox}[\questionboxheight]% \begin{solution}% \texttt{locking\_thread\_a()} locks \texttt{mutex1} then waits for \texttt{mutex2}, which can be held by \texttt{locking\_thread\_b()}, whereas \texttt{locking\_thread\_b()} locks \texttt{mutex2} then waits for \texttt{mutex1}, which can be held by \texttt{locking\_thread\_a()}. The result is that both threads are blocked, waiting for the availablity of a resource held by the other. \end{solution} \end{biganswerbox} \nopagebreak\mbox{}% %% \item Examine it carefully, and see if you can make a small change so %% that the program can run to completion. Show the change you made to %% your lab supervisor. \item Copy the original program (\texttt{deadlock.c}) to a new file name, and make simple changes to it to remove at least one of the conditions required for deadlock. \begin{explanation} Note that there are \emph{many} ways of solving this problem, not just one or two. Look for as many as you can. Use simple logic rather than spending a lot of time reading the manuals for new \POSIX threads library functions. First, simply try rearranging the pseudocode in program~\vref{prg:deadlock-pseudocode}. \end{explanation} \begin{itemize} \item Do this a number of times, using a different method for each program. \begin{solution} \item[\textbf{Solution:}] There are many solutions that may be correct, but many are wrong. For example, removing the call to \texttt{shed\_yield()} does not remove any conditions for deadlock, since the program is very likely to deadlock on an \SMP machine, and on a single \CPU machine if there is some real work for the threads to do. Program~\vref{prg:no-mutual-exclusion} shows the mutual exclusion removed. The \texttt{main()} fucntion is still the same as program~\vref{prg:deadlock-main-function}. Note that the danger here is that the data may be corrupted if two threads write to it at the same time, or there may be a race condition that results in the wrong values stored in or read from the data. Program~\vref{prg:no-hold-and-wait} removes hold and wait from \texttt{locking\_thread\_a()}. Note that this would not always be possible; the two resources may be both needed at the same time. Program~\vref{prg:no-hold-and-wait-by-voluntarily-releasing} is based on \texttt{backoff.c}; see section~\vref{sec:other-resources}. The idea is that if \texttt{locking\_thread\_a()} has \texttt{mutex1}, and cannot lock \texttt{mutex2}, it releases \texttt{mutex1} and tries again later. This eliminates hold and wait, since \texttt{locking\_thread\_a()} does not continue holding \texttt{mutex1} while waiting for \texttt{mutex2}. Have a good look at the code, and see how it works. Porgram~\vref{prg:no-circular-wait} simply re-orders the requests for resources so that all threads request them in the same order. This is a common practice in operating systems and threaded applications, but there may be some situations where it cannot be done. \item[\textbf{End of Solution.}] \mbox{} \begin{program} \vspace*{-2ex} %[ #include #include #include "errors.h" void *locking_thread_a( void *arg ) { int status; printf( "(NON) locking_thread_a starting\n" ); printf( "a is using data 1\n" ); sched_yield(); printf( "a now using data 2\n" ); printf( "(NON) locking_thread_a finishing\n" ); pthread_exit( NULL ); } void *locking_thread_b( void *arg ) { int status; printf( "(NON) locking_thread_b starting\n" ); printf( "b is now using data 2\n" ); sched_yield(); printf( "b now using data 1\n" ); printf( "(NON) locking_thread_b finishing\n" ); pthread_exit( NULL ); } %] \vspace*{-3ex} %\end{verbatim} \caption{This simple solution removes mutual exclusion by removing the mutexes. Note that although this leaves nothing much, in real life a mutex is associated with data, and the data would be accessed, even though it is no longer protected by a mutex.} \label{prg:no-mutual-exclusion} \end{program} \begin{program}[tbh] \vspace*{-2ex} %[ #include #include #include "errors.h" /* Initialize 2 mutexes. */ pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; void *locking_thread_a( void *arg ) { int status; printf( "locking_thread_a starting\n" ); status = pthread_mutex_lock( &mutex1 ); if ( status != 0 ) err_abort( status, "lock 1" ); printf( "a has lock 1\n" ); sched_yield(); // Note: unlock mutex1 before lock mutex 2: pthread_mutex_unlock( &mutex1 ); printf( "a now trying to get lock 2\n" ); status = pthread_mutex_lock( &mutex2 ); printf( "a has lock 2\n" ); if ( status != 0 ) err_abort( status, "lock 2" ); pthread_mutex_unlock( &mutex2 ); printf( "locking_thread_a finishing\n" ); pthread_exit( NULL ); } void *locking_thread_b( void *arg ) { int status; printf( "locking_thread_b starting\n" ); status = pthread_mutex_lock( &mutex2 ); if ( status != 0 ) err_abort( status, "lock 2" ); printf( "b has lock 2\n" ); sched_yield(); printf( "b now trying to get lock 1\n" ); status = pthread_mutex_lock( &mutex1 ); if ( status != 0 ) err_abort( status, "lock 1" ); printf( "b has lock 1\n" ); pthread_mutex_unlock( &mutex2 ); pthread_mutex_unlock( &mutex1 ); printf( "locking_thread_b finishing\n" ); pthread_exit( NULL ); } %] \vspace*{\bottomspace} %\end{verbatim} \caption{Here we removed the condition of hold and wait from \texttt{locking\_thread\_a()}, by simply unlocking \texttt{mutex1} before locking \texttt{mutex2}.} \label{prg:no-hold-and-wait} \end{program} \end{solution} \begin{program}[tbh] \vspace*{-2ex} %[ #include #include #include "errors.h" /* Initialize 2 mutexes. */ pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; void *locking_thread_a( void *arg ) { printf( "locking_thread_a starting\n" ); for (;;) { int status; status = pthread_mutex_lock( &mutex1 ); if ( status != 0 ) err_abort( status, "lock 1" ); printf( "a has lock 1\n" ); sched_yield(); printf( "a now trying to get lock 2\n" ); status = pthread_mutex_trylock( &mutex2 ); if ( status == 0 ) break; printf( "a failed to lock; backing off\n" ); pthread_mutex_unlock( &mutex1 ); sched_yield(); } printf( "a has lock 2\n" ); pthread_mutex_unlock( &mutex1 ); pthread_mutex_unlock( &mutex2 ); printf( "locking_thread_a finishing\n" ); pthread_exit( NULL ); } void *locking_thread_b( void *arg ) { int status; printf( "locking_thread_b starting\n" ); status = pthread_mutex_lock( &mutex2 ); if ( status != 0 ) err_abort( status, "lock 2" ); printf( "b has lock 2\n" ); sched_yield(); printf( "b now trying to get lock 1\n" ); status = pthread_mutex_lock( &mutex1 ); if ( status != 0 ) err_abort( status, "lock 1" ); printf( "b has lock 1\n" ); pthread_mutex_unlock( &mutex2 ); pthread_mutex_unlock( &mutex1 ); printf( "locking_thread_b finishing\n" ); pthread_exit( NULL ); } %] \vspace*{\bottomspace} %\end{verbatim} \caption{This method is based on \texttt{backoff.c}; see section~\vref{sec:other-resources}.} \label{prg:no-hold-and-wait-by-voluntarily-releasing} \end{program} \begin{program}[tbh] \vspace*{-2ex} %[ #include #include #include "errors.h" /* Initialize 2 mutexes. */ pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER; void *locking_thread_a( void *arg ) { int status; printf( "locking_thread_a starting\n" ); status = pthread_mutex_lock( &mutex1 ); if ( status != 0 ) err_abort( status, "lock 1" ); printf( "a has lock 1\n" ); sched_yield(); printf( "a now trying to get lock 2\n" ); status = pthread_mutex_lock( &mutex2 ); printf( "a has lock 2\n" ); if ( status != 0 ) err_abort( status, "lock 2" ); pthread_mutex_unlock( &mutex1 ); pthread_mutex_unlock( &mutex2 ); printf( "locking_thread_a finishing\n" ); pthread_exit( NULL ); } void *locking_thread_b( void *arg ) { int status; printf( "locking_thread_b starting\n" ); status = pthread_mutex_lock( &mutex1 ); if ( status != 0 ) err_abort( status, "lock 1" ); printf( "a has lock 1\n" ); sched_yield(); printf( "a now trying to get lock 2\n" ); status = pthread_mutex_lock( &mutex2 ); printf( "a has lock 2\n" ); if ( status != 0 ) err_abort( status, "lock 2" ); pthread_mutex_unlock( &mutex1 ); pthread_mutex_unlock( &mutex2 ); printf( "locking_thread_b finishing\n" ); pthread_exit( NULL ); } %] \vspace*{-3ex} %\end{verbatim} \caption{By re-ordering the reequest for resources, we can eliminate the condition of circular wait. We simply request the resources in the same order.} \label{prg:no-circular-wait} \end{program} \item Put a comment at the top of the program, indicating which conditions for deadlock you have removed. \item Put your name and class in a comment at the top of each file. \item Demonstrate to your lab supervisor. \item \label{que:make-text-file}Write a text file containing your answers to questions~\ref{que:mutual-exclusion}, \ref{que:hold-and-wait}, \ref{que:no-preemption} and \ref{que:circular-wait}. \item Use the \texttt{tar} or \texttt{zip} program to combine the sources and the text file mentioned above into one file, and \item Submit to the online submission system at \sloppypar\url{http://nicku.org/perl2/submit.cgi}. \end{itemize} \end{enumerate} \clearpage \begin{program}[t] %\smallskip% %\begin{verbatim} \vspace*{-2ex} %[ int main() { pthread_t thread1, thread2; int status; printf( "Creating thread a\n" ); status = pthread_create( &thread1, NULL, locking_thread_a, NULL ); if ( status ) err_abort( status, "locking_thread_a" ); printf( "Creating thread b\n" ); status = pthread_create( &thread2, NULL, locking_thread_b, NULL ); if ( status ) err_abort( status, "locking_thread_b" ); pthread_exit( NULL ); } %] \vspace*{-3ex} %\end{verbatim} \caption{The second part of the program \texttt{deadlock.c} that is unable to run to completion.} \label{prg:deadlock-main-function} \end{program} \newpage \subsection{Some Other Resources} \label{sec:other-resources} Some students wanted to know how you check if a mutex is locked without the calling thread going to sleep if the mutex is already locked by another thread. The answer is the \POSIX threads library function: \begin{verbatim} int pthread_mutex_trylock( pthread_mutex_t *MUTEX ); \end{verbatim} which immediately returns the value \texttt{EBUSY} if the mutex is locked. You can see an interesting example from the file \texttt{backoff.c}, shown on pages 66--69 of Butenhof (see \cite{bib:But97} below). You can read \texttt{backoff.c} from the subject web site at \texttt{http://nicku.org/\allowbreak ossi/\allowbreak{}lectures/\allowbreak{}processes/\allowbreak programming-posix-threads/\allowbreak{}backoff.c}. %% \section{References} %% \label{sec:references} %% There are many good sources of information in the library and on the %% Web about processes and threads. Here are some I recommend: %% \begin{itemize} %% \item\url{http://www.llnl.gov/computing/tutorials/workshops/workshop/pthreads/MAIN.html} %% gives a {good online tutorial} about \POSIX threads. %% \item \url{http://www.humanfactor.com/pthreads/} provides links to a %% lot of information about \POSIX threads %% \item The {best book} about \POSIX threads is %% \emph{Programming with POSIX Threads}, David Butenhof, %% Addison-Wesley, May 1997. Even though it was written so long ago, %% David wrote much of the \POSIX threads standard, so it really is the %% definitive work. It made me laugh, too! %% \item \emph{Operating Systems: A Modern Perspective: Lab Update}, 2nd %% Edition, Gary Nutt, Addison-Wesley, 2002. A nice text book that %% emphasises the practical (like I do!) %% \end{itemize} \begin{thebibliography}{widest} \label{sec:references} \item[]There are many good sources of information in the library and on the Web about processes and threads. Here are some I recommend: \bibitem[tut]{bib:tut}\url{http://www.llnl.gov/computing/tutorials/workshops/workshop/pthreads/MAIN.html} gives a {good online tutorial} about \POSIX threads. \bibitem[links]{bib:links}\url{http://www.humanfactor.com/pthreads/} provides links to a lot of information about \POSIX threads \bibitem[But97]{bib:But97} The {best book} about \POSIX threads is \emph{Programming with POSIX Threads}, David Butenhof, Addison-Wesley, May 1997. Even though it was written so long ago, David wrote much of the \POSIX threads standard, so it really is the definitive work. It made me laugh, too! \bibitem[Nut02]{bib:Nut02} \emph{Operating Systems: A Modern Perspective: Lab Update}, 2nd Edition, Gary Nutt, Addison-Wesley, 2002. A nice text book that emphasises the practical (like I do!) \end{thebibliography} \end{document}