cancel與 pthread

pthread_cancel(pthread_t pd);

用於取消一個函數,它通常需要被取消線程的配合

默認情況(延遲取消),它就是給pd設置取消標誌, pd線程在很多時候會查看自己是否有取消請求

如果有就主動退出, 這些查看是否有取消的地方稱為取消點

如果是異步取消(pthread_setcanceltype設置),那麼 pthread_cancel同時還給發送信號通知對方

對方理解會結束自己

pthread_cancel (pd):

設置pd的請求標誌

如果是異步模式,發送信號

pd收到信號後退出(線程如何退出參考 pthread_create )

取消點的本質就是修改取消模式為異步模式,並檢查是否有未決取消請求

  1. int
  2. attribute_hidden
  3. __pthread_enable_asynccancel (void)
  4. {//
  5. struct pthread *self = THREAD_SELF;
  6. int oldval = THREAD_GETMEM (self, cancelhandling);
  7. while (1)
  8. {
  9. int newval = oldval | CANCELTYPE_BITMASK;
  10. 已經是異步模式,無須任何檢查,因為異步模式中線程只要收到請求,就會退出
  11. if (newval == oldval)
  12. break;
  13. int curval = THREAD_ATOMIC_CMPXCHG_VAL (self, cancelhandling, newval,
  14. oldval);
  15. if (__builtin_expect (curval == oldval, 1))
  16. {
  17. if (CANCEL_ENABLED_AND_CANCELED_AND_ASYNCHRONOUS (newval))
  18. {
  19. THREAD_SETMEM (self, result, PTHREAD_CANCELED);
  20. __do_cancel ();
  21. }
  22. break;
  23. }
  24. /* Prepare the next round. */
  25. oldval = curval;
  26. }
  27. return oldval;
  28. }

如果原來是異步取消類型,那麼pthread_cancel將直接導致它被取消,所以根本不需要主動檢查

(CANCELTYPE_BIT標示異步), 否則設置並檢查

很多地方都是包含取消點,包括

pthread_join()、pthread_testcancel()、pthread_cond_wait()、 pthread_cond_timedwait()、sem_wait()、sigwait()

write,read,大多數會阻塞的系統調用

pthread_join:

/* Switch to asynchronous cancellation. */

int oldtype = CANCEL_ASYNC ();

等待線程結束

/* Restore cancellation mode. */

CANCEL_RESET (oldtype);

write等的實現在

./nptl/sysdeps/unix/sysv/linux/i386/sysdep-cancel.h 中

本質就是

old = enable async type

syscall

restore old type

xcm@u32:~/test/pthread$ cat write.c

int main()

{

write(1, "\n", 1);

}

xcm@u32:~/test/pthread$ gcc write.c -static

xcm@u32:~/test/pthread$ objdump -d >tmp

  1. 0804f8a0 <__libc_write>:
  2. %gs:0xc檢查是否多線程,單線程沒有必要
  3. 804f8a0: 65 83 3d 0c 00 00 00 cmpl $0x0,%gs:0xc
  4. 804f8a7: 00
  5. 804f8a8: 75 21 jne 804f8cb <__write_nocancel/>
  6. 0804f8aa <__write_nocancel>:
  7. 804f8aa: 53 push %ebx
  8. 804f8ab: 8b 54 24 10 mov 0x10(%esp),%edx
  9. 804f8af: 8b 4c 24 0c mov 0xc(%esp),%ecx
  10. 804f8b3: 8b 5c 24 08 mov 0x8(%esp),%ebx
  11. 804f8b7: b8 04 00 00 00 mov $0x4,%eax
  12. 804f8bc: cd 80 int $0x80
  13. 804f8be: 5b pop %ebx
  14. 804f8bf: 3d 01 f0 ff ff cmp $0xfffff001,%eax
  15. 804f8c4: 0f 83 36 20 00 00 jae 8051900 <__syscall_error/>
  16. 804f8ca: c3 ret
  17. 804f8cb: e8 30 0e 00 00 call 8050700 <__libc_enable_asynccancel/>

默認情況下,只要有取消請求,並且遇到取消點就會退出

所以如果一個線程不到達取消點, 比如僅僅操作一些數據結構就沒有被中斷的風險

否則容易死鎖

  1. #include
  2. #include
  3. #include
  4. #include
  5. #include
  6. pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
  7. void unlock(void *unused)
  8. {
  9. pthread_mutex_unlock(&lock);
  10. }
  11. void *threadfunc(void *parm)
  12. {
  13. while (1)
  14. {
  15. //pthread_cleanup_push(unlock, 0);
  16. pthread_mutex_lock(&lock);
  17. write(111, "\n", 1);
  18. pthread_mutex_unlock(&lock);
  19. //pthread_cleanup_pop(0);
  20. }
  21. return 0;
  22. }
  23. int main(int argc, char **argv)
  24. {
  25. pthread_t thread;
  26. while (1)
  27. {
  28. pthread_create(&thread, NULL, threadfunc, NULL);
  29. pthread_cancel(thread);
  30. pthread_join(thread, 0);
  31. printf("locking ..\n");
  32. pthread_mutex_lock(&lock);
  33. printf("done\n");
  34. pthread_mutex_unlock(&lock);
  35. }
  36. return 0;
  37. }

把註釋去掉就OK了,它在退出之前會先執行其cleanup函數


分享到:


相關文章: