CS 3733 Operating Systems Notes: USP Chapter 12 - Threads

Overview: threads vs. processes (created with fork)

Look at the programs getsizeschild, getsizesthread and getsizescall.

USP Chapter 12: POSIX Threads:

Motivating problem: monitor multiple file descriptors:

Methods you should know about:

Other methods: A thread has its own program counter and stack, but shares a number of resources with its process and other threads of the process: When a new thread is created it runs concurrently with the creating process.
When creating a thread you indicate which function the thread should execute.

Consider a function processfd called as an ordinary function and as a created thread.


Figure 12.1 (page 412): Program that makes an ordinary call to processfd has a single thread of execution.


Figure 12.2 (page 412): Program that creates a new thread to execute processfd has two threads of execution.


A function that is used as a thread must have a special format.
It takes a single parameter of type pointer to void and returns a pointer to void.

The parameter type allows any pointer to be passed.
This can point to a structure, so in effect, the function can use any number of parameters.
We will discuss the meaning of the return value later.

The processfd function used above might have prototype:
void *processfd(void *arg);
Instead of passing the file descriptor to be monitored directly, we pass a pointer to it.
The function my access the parameter as follows:
       int fd;
       fd = *((int *)arg);

Creating a thread:

   void *processfd(void *arg);

   int error;
   int fd;
   pthread_t tid;

   if (error = pthread_create(&tid, NULL, processfd, &fd))
      fprintf(stderr, "Failed to create thread: %s\n", strerror(error));
Look at Program 12.1, processfd.

Program 12.2, monitorfd.c is a function that creates threads to monitor an array of open file descriptors.
The function is passed an array of file descriptors and the size of the array.
For each file descriptor it creates a thread and then waits for all of the threads to complete.

12.3 Thread Management

POSIX functiondescription
pthread_cancelterminate another thread
pthread_createcreate a thread
pthread_detachset thread to release resources
pthread_equaltest two thread IDs for equality
pthread_exitexit a thread without exiting process
pthread_killsend a signal to a thread
pthread_joinwait for a thread
pthread_selffind out own thread ID

Most POSIX functions return 0 on success and a nonzero error code on failure.
They do not set errno but the value returned when an error occurs has the value that errno would have.
None of the POSIX thread functions ever return the error EINTR.

Most of the pthread functions are declared in pthread.h.

12.3.1 The Thread ID
Each thread has an id of type pthread_t.
On most systems this is just an integer (like a process ID) but it does not have to be.

A thread can get its ID with pthread_self and IDs can be compared with pthread_equal

    pthread_t pthread_self(void);
    int pthread_equal(thread_t t1, pthread_t t2);

12.3.2 Creating a thread
A thread is created with pthread_create

   int pthread_create(pthread_t *restrict thread,
                      const pthread_attr_t *restrict attr,
                      void *(*start_routine)(void *), void *restrict arg);
We saw an example in Program 12.2, monitorfd.

12.3.3 Detaching and joining

   int pthread_detach(pthread_t thread);
Example 12.5 shows how to make a thread detached:
   void *processfd(void *arg);

   int error;
   int fd
   pthread_t tid;

   if (error = pthread_create(&tid, NULL, processfd, &fd))
      fprintf(stderr, "Failed to create thread: %s\n", strerror(error));
   else if (error = pthread_detach(tid))
      fprintf(stderr, "Failed to detach thread: %s\n", strerror(error));
Example 12.6 shows how a thread can detach itself:
   void *detachfun(void *arg) {
      int i = *((int *)(arg));
      if (!pthread_detach(pthread_self())) 
         return NULL;
      fprintf(stderr, "My argument is %d\n", i);
      return NULL;
   }
   int pthread_join(pthread_t thread, void **value_ptr);
Example 12.7 shows how you might do this:
   int error;
   int *exitcodep;
   pthread_t tid;

   if (error = pthread_join(tid, &exitcodep))
      fprintf(stderr, "Failed to join thread: %s\n", strerror(error));
   else
      fprintf(stderr, "The exit code was %d\n", *exitcodep);

12.3.4 Exiting and Cancellation
A process can terminate when:

In any of these cases, all threads of the process terminate.

When a thread is done, it can call return from its main function (the one used by pthread_create) or it can call pthread_exit

   void pthread_exit(void *value_ptr);
One thread can request that another exit with pthread_cancel
   int pthread_cancel(pthread_t thread);
The pthread_cancel returns after making the request.
A successful return does not mean that the target thread has terminated or even that it eventually will terminate as a result of the request.


Stop here for now ...


The result of the request depends on the target thread's type and state.

The possible states are

This is similar to blocking and unblocking signals in that if it is in the disable state, it will receive the cancel request when it enters the enable state.
The default is PTHREAD_CANCEL_ENABLE

A thread can change its state with:

   int pthread_setcancelstate(int state, int *oldstate);

Program 12.3, processfdcancel, goes into the disable state while it is executing docommand.

The possible cancellation types are

Calls to certain blocking functions cause cancellation points.
You can set a cancellation point with pthread_testcancel.
You can set the cancellation type with pthread_setcanceltype
   int pthread_setcanceltype(int type, int *oldtype);
   void pthread_testcancel(void);

12.3.5 Passing parameters and returning values
You can pass multiple parameters to a thread by passing a pointer, such as an array, to the thread when it is created.

Care must be taken in receiving return values.

Program 12.4, callcopymalloc creates a thread to copy a file. Look at Program 12.4

Where is the number of bytes copied stored?
It cannot be in an automatic variable in copyfilemalloc.
copyfilemalloc calls malloc to create space for the value.
The calling program must free this space if it is going to run for a long time.

Look at Program 12.5, copyfilemalloc

An alternate is for the creating thread to allocate the space for the return value and tell the thread where this is (using the thread parameter).

Program 12.6, callcopypass passes an array of 3 integers to the created thread.
The thread stores the return value in the last entry of the array.

Look at Program 12.6 and Program 12.7, copyfilepass

We could have used an array of size 2.

When creating multiple threads that are to be joined, you must

Look at program 12.8, copymultiple

Look at program 12.9, badparameters.
This program creates a number of threads, passing a pointer to the loop index as a parameter.

Exercise 12.14: What can you return from this thread:

void *whichexit(void *arg) {
   int n;
   int np1[1];
   int *np2;
   char s1[10];
   char s2[] = "I am done";
   n = 3;
   np1[0] = n;
   np2 = (int *)malloc(sizeof(int));
   *np2 = n;
   strcpy(s1, "Done");
   return(NULL);
} 
  1. n
  2. &n
  3. (int *)n
  4. np1
  5. np2
  6. s1
  7. s2
  8. "This works"
  9. strerror(EINTR)

Cancellation Points

POSIX functions that are always cancelation points.
accept mq_timedsend putpmsg sigsuspend
aio_suspend msgrcv pwrite sigtimedwait
clock_nanosleep msgsnd read sigwait
close msync readv sigwaitinfo
connect nanosleep recv sleep
creat open recvfrom system
fcntl* pause recvmsg tcdrain
fsync poll select usleep
getmsg pread sem_timedwait wait
getpmsg pthread_cond_timedwait sem_wait waitid
lockf pthread_cond_wait send waitpid
mq_receive pthread_join sendmsg write
mq_send pthread_testcancel sendto writev
mq_timedreceive putmsg sigpause
* only when the command is F_SETLKW

POSIX functions that may be cancellation points in some implementations.
catclose ftell getwc pthread_rwlock_wrlock
catgets ftello getwchar putc
catopen ftw getwd putc_unlocked
closedir fwprintf glob putchar
closelog fwrite iconv_close putchar_unlocked
ctermid fwscanf iconv_open puts
dbm_close getc ioctl pututxline
dbm_delete getc_unlocked lseek putwc
dbm_fetch getchar mkstemp putwchar
dbm_nextkey getchar_unlocked nftw readdir
dbm_open getcwd opendir readdir_r
dbm_store getdate openlog remove
dlclose getgrent pclose rename
dlopen getgrgid perror rewind
endgrent getgrgid_r popen rewinddir
endhostent getgrnam posix_fadvise scanf
endnetent getgrnam_r posix_fallocate seekdir
endprotoent gethostbyaddr posix_madvise semop
endpwent gethostbyname posix_spawn setgrent
endservent gethostent posix_spawnp sethostent
endutxent gethostname posix_trace_clear setnetent
fclose getlogin posix_trace_close setprotoent
fcntl* getlogin_r posix_trace_create setpwent
fflush getnetbyaddr posix_trace_create_withlog setservent
fgetc getnetbyname posix_trace_eventtypelist_getnext_id setutxent
fgetpos getnetent posix_trace_eventtypelist_rewind strerror
fgets getprotobyname posix_trace_flush syslog
fgetwc getprotobynumber posix_trace_get_attr tmpfile
fgetws getprotoent posix_trace_get_filter tmpnam
fopen getpwent posix_trace_get_status ttyname
fprintf getpwnam posix_trace_getnext_event ttyname_r
fputc getpwnam_r posix_trace_open ungetc
fputs getpwuid posix_trace_rewind ungetwc
fputwc getpwuid_r posix_trace_set_filter unlink
fputws gets posix_trace_shutdown vfprintf
fread getservbyname posix_trace_timedgetnext_event vfwprintf
freopen getservbyport posix_typed_mem_open vprintf
fscanf getservent printf vwprintf
fseek getutxent pthread_rwlock_rdlock wprintf
fseeko getutxid pthread_rwlock_timedrdlock wscanf
fsetpos getutxline pthread_rwlock_timedwrlock
* for any value of the command argument

No other POSIX functions are allowed to introduce cancellation points

12.4 Thread Safety

Almost all system functions and library functions are safe to use with threads.

The exceptions are given in the following table.

POSIX functions that are not required to be thread-safe.
asctimefcvtgetpwnamnl_langinfo
basenameftwgetpwuidptsname
catgetsgcvtgetservbynameputc_unlocked
cryptgetc_unlockedgetservbyportputchar_unlocked
ctimegetchar_unlockedgetserventputenv
dbm_clearerrgetdategetutxentpututxline
dbm_closegetenvgetutxidrand
dbm_deletegetgrentgetutxlinereaddir
dbm_errorgetgrgidgmtimesetenv
dbm_fetchgetgrnamhcreatesetgrent
dbm_firstkeygethostbyaddrhdestroysetkey
dbm_nextkeygethostbynamehsearchsetpwent
dbm_opengethostentinet_ntoasetutxent
dbm_storegetloginl64astrerror
dirnamegetnetbyaddrlgammastrtok
dlerrorgetnetbynamelgammafttyname
drand48getnetentlgammalunsetenv
ecvtgetoptlocaleconvwcstombs
encryptgetprotobynamelocaltimewctomb
endgrentgetprotobynumberlrand48
endpwentgetprotoentmrand48
endutxentgetpwentnftw

12.5 User Threads and Kernel Threads

User-level threads run on top of an operating system.

Kernel-level threads are part of the OS.

Figure 12.3 (page 433) User-level threads.

Figure 12.4 (page 434) Kernel-level threads.

With a hybrid model, the threads of a process can share several kernel entities.

Figure 12.5 (page 435) A hybrid model.


POSIX threads can support any of these models.

A thread has a contentionscope of PTHREAD_SCOPE_PROCESS or PTHREAD_SCOPE_SYSTEM.

One or both can be supported on a given system.

Thread Attributes

Attributes behave like objects that can be created or destroyed.

Settable properties of thread attributes.
propertyfunction
attribute objectspthread_attr_destroy
pthread_attr_init
statepthread_attr_getdetachstate
pthread_attr_setdetachstate
stack pthread_attr_getguardsize
pthread_attr_setguardsize
pthread_attr_getstack
pthread_attr_setstack
scheduling pthread_attr_getinheritsched
pthread_attr_setinheritsched
pthread_attr_getschedparam
pthread_attr_setschedparam
pthread_attr_getschedpolicy
pthread_attr_setschedpolicy
pthread_attr_getscope
pthread_attr_setscope
Initialize or destroy an attribute with:
   int pthread_attr_destroy(pthread_attr_t *attr);
   int pthread_attr_init(pthread_attr_t *attr);

12.6.1 The thread state
The state is either PTHREAD_CREATE_JOINABLE (the default) or PTHREAD_CREATE_DETACHED.

  int pthread_attr_getdetachstate(const pthread_attr_t *attr,
                                  int *detachstate);
  int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
Example 12.15: create a detached thread:
   int error, fd;
   pthread_attr_t tattr;
   pthread_t tid;

   if (error = pthread_attr_init(&tattr))
      fprintf(stderr, "Failed to create attribute object: %s\n",
                       strerror(error));
   else if (error = pthread_attr_setdetachstate(&tattr,
                    PTHREAD_CREATE_DETACHED))
      fprintf(stderr, "Failed to set attribute state to detached: %s\n",
              strerror(error));
   else if (error = pthread_create(&tid, &tattr, processfd, &fd))
      fprintf(stderr, "Failed to create thread: %s\n", strerror(error));

The thread stack
You can set a location and size for the thread stack.

  int pthread_attr_getstack(const pthread_attr_t *restrict attr,
           void **restrict stackaddr, size_t *restrict stacksize);
  int pthread_attr_setstack(pthread_attr_t *attr,
           void *stackaddr, size_t stacksize);
Some systems allow you to set a guard for the stack so that an overflow into the guard area can generate a SIGSEGV signal.
  int pthread_attr_getguardsize(const pthread_attr_t *restrict attr,
                            size_t *restrict guardsize);
  int pthread_attr_setguardsize(pthread_attr_t *attr,
                            size_t guardsize);

12.6.3 Thread scheduling
The contentionscope can be PTHREAD_SCOPE_PROCESS or PTHREAD_SCOPE_SYSTEM.

The scope determines whether the thread competes with other threads of the process or with other processes in the system.

  int pthread_attr_getscope(const pthread_attr_t *restrict attr,
                            int *restrict contentionscope);
  int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);

Example 12.16: create a thread that contends with other processes

   int error;
   int fd;
   pthread_attr_t tattr;
   pthread_t tid;

   if (error = pthread_attr_init(&tattr))
      fprintf(stderr, "Failed to create an attribute object:%s\n",
              strerror(error));
   else if (error = pthread_attr_setscope(&tattr, PTHREAD_SCOPE_SYSTEM))
      fprintf(stderr, "Failed to set scope to system:%s\n",
              strerror(error));
   else if (error = pthread_create(&tid, &tattr, processfd, &fd))
      fprintf(stderr, "Failed to create a thread:%s\n", strerror(error));

Inheritance of scheduling policy:
With PTHREAD_INHERIT_SCHED the scheduling attributes of the attribute object are ignored and the created thread has the same scheduling attributes of the creating thread.
With PTHREAD_EXPLICIT_SCHED the scheduling is taken from the attribute.

  int pthread_attr_getinheritsched(const pthread_attr_t *restrict attr,
                            int *restrict inheritsched);
  int pthread_attr_setinheritsched(pthread_attr_t *attr,
                            int inheritsched);

Scheduling parameters and policy. Typical scheduling policies are SCHED_FIFO, SCHED_RR> and SCHED_OTHER.
What is supported is system dependent.
Each policy is modified by scheduling parameters stored in a struct sched_param
This may contain a priority or a quantum value.

  int pthread_attr_getschedparam(const pthread_attr_t *restrict attr,
                            struct sched_param *restrict param);
  int pthread_attr_setschedparam(pthread_attr_t *restrict attr,
                            const struct sched_param *restrict param);
  int pthread_attr_getschedpolicy(const pthread_attr_t *restrict attr,
                                 int *restrict policy);
  int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);