Wednesday, October 22, 2014

Unix Prog: Thread Termination(2)

1. pthread_cancel, pthread_cleanup_push, pthread_cleanup_popup

Definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/pthread.h  
 ......  
 /* Cancel THREAD immediately or at the next possibility. */  
 extern int pthread_cancel (pthread_t __th);  
 ......  
 /* Install a cleanup handler: ROUTINE will be called with arguments ARG  
   when the thread is canceled or calls pthread_exit. ROUTINE will also  
   be called with arguments ARG when the matching pthread_cleanup_pop  
   is executed with non-zero EXECUTE argument.  
   
   pthread_cleanup_push and pthread_cleanup_pop are macros and must always  
   be used in matching pairs at the same nesting level of braces. */  
 # define pthread_cleanup_push(routine, arg) \  
  do {                                    \  
   __pthread_cleanup_class __clframe (routine, arg)  
   
 /* Remove a cleanup handler installed by the matching pthread_cleanup_push.  
   If EXECUTE is non-zero, the handler function is called. */  
 # define pthread_cleanup_pop(execute) \  
   __clframe.__setdoit (execute);                      \  
 ......  

By default, pthread_cancel will make the thread with id specified by __th behave as if it had called pthread_exit with an argument of PTHREAD_CANCELED. And calling thread just make the request, it doesn't wait.

We can push cleanup handler into a stack, and when we call pthread_cleanup_pop, handler will be called in the reversed order when putting in.

Clean up handler will be called when:
1) makes a call to pthread_exit
2) responds to cancellation request
3) makes a call to pthread_cleanup_pop with a non-zero execute argument
(Note: if execute argument is not 0, then the handler is not called, but handler will still be removed from stack)

Example:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<string.h>  
 #include<pthread.h>  
   
 void cleanup(void *arg)  
 {  
  printf("cleanup: %s\n", (char*)arg);  
 }  
   
 void* thr_fn1(void* arg)  
 {  
  // Push the clean up handlers  
  printf("thread 1 start.\n");  
  pthread_cleanup_push(cleanup, (void*)"Thread 1 first handler!\n");  
  pthread_cleanup_push(cleanup, (void*)"Thread 1 second handler!\n");  
  printf("thread 1 push complete.\n");  
   
  if(arg) return ((void*) 1);  
   
  // Note: we have to provide the pthread_cleanup_pop here, otherwise  
  // code won't compile. Since pthread_cleanup_push is defined as macro,  
  // and after expansion, each pthread_cleanup_push needs one pthread_cleanup_pop  
  // at the same level.  
   
  pthread_cleanup_pop(0);  
  pthread_cleanup_pop(0);  
  return((void*)1);  
 }  
   
 void* thr_fn2(void* arg)  
 {  
  // Push the clean up handlers  
  printf("thread 2 start.\n");  
  pthread_cleanup_push(cleanup, (void*)"Thread 2 first handler!\n");  
  pthread_cleanup_push(cleanup, (void*)"Thread 2 second handler!\n");  
  printf("thread 2 push complete.\n");  
   
  if(arg) pthread_exit((void*)2);  
   
  pthread_cleanup_pop(0);  
  pthread_cleanup_pop(0);  
  return((void*)1);  
 }  
   
 int main(int argc, char* argv[])  
 {  
  int err;  
  pthread_t tid1, tid2;  
  void *tret;  
   
  // Launch the 1st thread  
  err = pthread_create(&tid1, NULL, thr_fn1, (void*)1);  
  if(err != 0) {  
   printf("can't create thread 1: %s\n", strerror(err));  
   exit(1);  
  }  
   
  // Launch the 2nd thread  
  err = pthread_create(&tid2, NULL, thr_fn2, (void*)1);  
  if(err != 0) {  
   printf("can't create thread 2: %s\n", strerror(err));  
   exit(1);  
  }  
   
  // Wait for the 1st thread finish  
  err = pthread_join(tid1, &tret);  
  if(err != 0) {  
   printf("can't join with thread 1: %s\n", strerror(err));  
   exit(2);  
  }  
   
  printf("thread 1 exit code: %ld\n", (long)tret);  
   
  // Wait for the 2nd thread finish  
  err = pthread_join(tid2, &tret);  
  if(err != 0) {  
   printf("can't join with thread 2: %s\n", strerror(err));  
   exit(2);  
  }  
   
  printf("thread 2 exit code: %ld\n", (long)tret);  
  exit(0);  
 }  

shell:
1) After we launching the thread 1 and thread 2. By OS scheduler, thread 2 runs firstly. When thread 2 calls pthread_exit, it calls clean up handler in the reversed way.
2) Then thread 1 runs. But it doesn't call clean up handler since it is using "return" instead of pthread_exit.
3) Then the main program use pthread_join to get the thread 1 's exit code
4) Lastly the main program use pthread_join to get the thread 2 's exit code
 ubuntu@ip-172-31-23-227:~$ ./thread.out  
 thread 2 start.  
 thread 2 push complete.  
 cleanup: Thread 2 second handler!  
   
 cleanup: Thread 2 first handler!  
   
 thread 1 start.  
 thread 1 push complete.  
 thread 1 exit code: 1  
 thread 2 exit code: 2  

2. pthread_detach
When a thread is created, by default it is joinable thread. This means that after the thread finishes, the calling thread still need to use pthread_join to ask os to reclaim the storage and do the clean up jobs.
If the thread is created with detachable state, then after it finishes, it will immediately reclaim all storage and do the clean up job.

We can call pthread_detach to make a thread into detached state to ask os reclaim its storage and do cleanup jobs after thread is finished.

Definition:
 ubuntu@ip-172-31-23-227:~$ less /usr/include/pthread.h  
 ......  
 /* Indicate that the thread TH is never to be joined with PTHREAD_JOIN.  
   The resources of TH will therefore be freed immediately when it  
   terminates, instead of waiting for another thread to perform PTHREAD_JOIN  
   on it. */  
 extern int pthread_detach (pthread_t __th) __THROW;  
 ......  

No comments:

Post a Comment