线程问题的核心: 怎么退出线程才是合适的----小话多线程(2)

2023-11-02

作者:陈曦

日期:2012-8-5  16:13:36

环境:[Mac 10.7.1 Lion Intel i3 支持64位指令 gcc4.2.1 xcode4.2 苹果开源代码Libc-763.11] 

转载请注明出处

每日总结:优秀的架构都是类似的,垃圾的架构一般都是一个原因:代码内部原理掌握得不够



Q1: main函数中最后用return 0; 和使用 exit(0); 退出进程到底有什么不同?

A: 一种很简单的区别方式就是return 0是返回给调用函数者,而exit(0)是直接返回给系统。但是,前者返回给什么函数?写如下代码:

  1. int main(int argc, char **argv)  
  2. {   
  3.       
  4.     return 0;       
  5. }  
  1. #include <stdlib.h>  
  2.   
  3. int main(int argc, char **argv)  
  4. {   
  5.     exit(0);      
  6. }  

分别编译成可执行文件。使用MachOView工具查看各自的可执行文件:

第一个对应的_start和_main汇编为:

首先要确定,程序的入口点默认是_start. 同时可以看到,main中最后只是简单的返回给调用者;但是start中首先调用了main,然后调用了exit,这说明了虽然main函数最后没做什么退出进程的事情,返回到start后依然会退出进程。

再看第二个:

可以看到,main最后调用了exit函数,start最后依然也调用了exit,但是start最后调用的exit已经无法运行到,因为main调用后进程就结束了。


由上面两种情况可以看出,main函数最后调用return 0; 或者 exit(0); 可以说是基本一样,除了程序流程有点变化,没有什么大的区别。



Q2: 主线程最后调用pthread_exit来退出可行吗?

A: 如果仅仅从退出主线程的角度来考虑,这是可行的; 但是,一般来说,进程应该总是主线程最后退出,这样比较符合编程的基本原则;如果这样的话,主线程使用pthread_exit退出就可能出问题: 因为,它虽然会释放已经注册的清理函数以及线程特有的数据,但是它并不一定会释放进程相关的资源(包括内存、信号量、互斥体等)或者执行进程注册的退出函数(如atexit注册的退出函数),这样就很有可能导致内存泄露。当然,上面说并不一定,是因为,如果主线程是进程中最后退出的线程,那么进程相关的资源和进程注册的退出函数才会被执行。为了明白pthread_exit内部调用关系,首先,我们下载苹果开源代码Libc-763.11,这是对应mac系统10.7.1的开源libc代码,如果是其它系统,请下载对应版本的代码。苹果开源代码网站: http://opensource.apple.com/

首先找到pthread_exit的实现代码:

  1. void  
  2. pthread_exit(void *value_ptr)  
  3. {  
  4.     pthread_t self = pthread_self();  
  5.     /* if the current thread is a workqueue thread, just crash the app, as per libdispatch folks */  
  6.     if (self->wqthread == 0) {  
  7.         _pthread_exit(self, value_ptr);  
  8.     } else {  
  9.         LIBC_ABORT("pthread_exit() may only be called against threads created via pthread_create()");  
  10.     }  
  11. }  

进入主要调用部分_pthread_exit函数:
  1. static void   
  2. _pthread_exit(pthread_t self, void *value_ptr)  
  3. {  
  4.     struct __darwin_pthread_handler_rec *handler;  
  5.     kern_return_t kern_res;  
  6.     int thread_count;  
  7.     int newstyle = self->newstyle;  
  8.   
  9.     /* Make this thread not to receive any signals */  
  10.     __disable_threadsignal(1);  
  11.   
  12. #if PTH_TRACE  
  13.     __kdebug_trace(0x900001c, self, newstyle, 0, 0, 0);  
  14. #endif  
  15.   
  16.     /* set cancel state to disable and type to deferred */  
  17.     _pthread_setcancelstate_exit(self, value_ptr, __unix_conforming);  
  18.   
  19.     while ((handler = self->__cleanup_stack) != 0)  
  20.     {  
  21.         (handler->__routine)(handler->__arg);   // call cleanup handler  
  22.         self->__cleanup_stack = handler->__next;  
  23.     }  
  24.     _pthread_tsd_cleanup(self); // Clean up thread specific data  
  25.   
  26.     if (newstyle == 0) {  
  27.         _pthread_reap_threads();  
  28.   
  29.         LOCK(self->lock);  
  30.         self->detached |= _PTHREAD_EXITED;  
  31.   
  32.         if (self->detached & PTHREAD_CREATE_JOINABLE) {  
  33.             mach_port_t death = self->death;  
  34.             self->exit_value = value_ptr;  
  35.             UNLOCK(self->lock);  
  36.             /* the joiner will need a kernel thread reference, leave ours for it */  
  37.             if (death) {  
  38.                 PTHREAD_MACH_CALL(semaphore_signal(death), kern_res);  
  39.                 if (kern_res != KERN_SUCCESS)  
  40.                     fprintf(stderr,  
  41.                         "semaphore_signal(death) failed: %s\n",  
  42.                         mach_error_string(kern_res));  
  43.             }  
  44.             LOCK(_pthread_list_lock);  
  45.             thread_count = --_pthread_count;  
  46.             UNLOCK(_pthread_list_lock);  
  47.         } else {  
  48.             UNLOCK(self->lock);  
  49.             LOCK(_pthread_list_lock);  
  50.             TAILQ_REMOVE(&__pthread_head, self, plist);  
  51. #if PTH_LISTTRACE  
  52.             __kdebug_trace(0x9000010, self, 0, 0, 5, 0);  
  53. #endif  
  54.             thread_count = --_pthread_count;  
  55.             UNLOCK(_pthread_list_lock);  
  56.             /* with no joiner, we let become available consume our cached ref */  
  57.             _pthread_become_available(self, self->kernel_thread);  
  58.         }  
  59.   
  60.         if (thread_count <= 0)      // if the thread is the last one, then exit the process  
  61.             exit(0);  
  62.   
  63.         /* Use a new reference to terminate ourselves. Should never return. */  
  64.         // internal terminate thread  
  65.         PTHREAD_MACH_CALL(thread_terminate(mach_thread_self()), kern_res);  
  66.         fprintf(stderr, "thread_terminate(mach_thread_self()) failed: %s\n",  
  67.                 mach_error_string(kern_res));  
  68.     } else {    // newstyle != 0  
  69.         semaphore_t joinsem = SEMAPHORE_NULL;  
  70.   
  71.         if ((self->joiner_notify == (mach_port_t)0) && (self->detached & PTHREAD_CREATE_JOINABLE))  
  72.             joinsem = new_sem_from_pool();  
  73.         LOCK(self->lock);  
  74.         self->detached |= _PTHREAD_EXITED;  
  75.   
  76.         self->exit_value = value_ptr;  
  77.         if (self->detached & PTHREAD_CREATE_JOINABLE) {  
  78.             if (self->joiner_notify == (mach_port_t)0) {  
  79.                 self->joiner_notify = joinsem;  
  80.                 joinsem = SEMAPHORE_NULL;  
  81.             }  
  82.             UNLOCK(self->lock);  
  83.             if (joinsem != SEMAPHORE_NULL)  
  84.                 restore_sem_to_pool(joinsem);  
  85.             _pthread_free_pthread_onstack(self, 0, 1);  
  86.         } else {  
  87.             UNLOCK(self->lock);  
  88.             /* with no joiner, we let become available consume our cached ref */  
  89.             if (joinsem != SEMAPHORE_NULL)  
  90.                 restore_sem_to_pool(joinsem);  
  91.             _pthread_free_pthread_onstack(self, 1, 1);  
  92.         }  
  93.     }  
  94.     LIBC_ABORT("thread %p didn't exit", self);  
  95. }  

为了更直观,我在上面的代码中加入了部分注释。从这里可以看出,pthread_exit做或不做如下几件事情:

1、已经注册的清理事件被执行;

2、线程私有数据被清理;

3、如果此线程是最后一个线程,那么会调用exit(0)退出进程。

4、不会释放进程相关的资源(如文件句柄、互斥体、内存等), 也不会执行进程注册的退出函数(如atexit注册的函数); 如果是最后一个线程,那么调用exit(0)将会做释放进程资源和执行进程注册的退出函数的事情。


由上面描述,pthread_exit还是一个比较安全的退出函数,但是主线程使用的话不太推荐,子线程使用还是没问题的。



Q3: 举个子线程跑完,主线程最后执行调用pthread_exit,而能调用进程资源释放的例子吧。

A: 

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <pthread.h>  
  4. #include <unistd.h>  
  5. #include <time.h>  
  6. #include <errno.h>  
  7. #include <string.h>  
  8.   
  9. #define PRINT_D(intValue)   printf(#intValue" is %d\n", (intValue));  
  10. #define PRINT_U(uintValue)   printf(#uintValue" is %lu\n", (uintValue));  
  11. #define PRINT_STR(str)      printf(#str" is %s\n", (str));  
  12. #define PRINT_S(str)      printf("%s\n", (#str));  
  13.   
  14. #define FOREVER_PRINT       { while(1)    printf("...");}  
  15.   
  16. #define RETURN_ERROR(func, ret, return_value)     \  
  17. if((ret) != 0)        \  
  18. {   \  
  19. perror(#func" error");  \  
  20. printf("ret is %d\n", (ret));   \  
  21. return (return_value);  \  
  22. }  
  23.   
  24. void    *thread_func(void *args)    
  25. {    
  26.     printf("son thread end...\n");  
  27.     return NULL;    
  28. }    
  29.   
  30. void    exit_process()  
  31. {  
  32.     PRINT_S("exit_process is called...")  
  33. }  
  34.   
  35. // main thread  
  36. int main(int argc, char **argv)  
  37. {   
  38.     pthread_t son_thread;  
  39.     int ret;    
  40.       
  41.     atexit(exit_process);  
  42.       
  43.     ret = pthread_create(&son_thread, NULL, thread_func, NULL);    
  44.     RETURN_ERROR(pthread_create, ret, -1)   
  45.     ret = pthread_detach(son_thread);  
  46.     RETURN_ERROR(pthread_detach, ret, -1)       
  47.   
  48.     sleep(2);  
  49.     pthread_exit(NULL);  
  50.       
  51.     printf("[Main thread]: end...\n");  
  52.       
  53.     return 0;      
  54. }  

主线程创建子线程后,睡眠2秒等待子线程完成,然后调用pthread_exit退出主线程。

执行结果:

[plain] view plain copy
  1. son thread end...  
  2. "exit_process is called..."  

进程注册的exit_process函数被执行了,这是因为子线程正常结束了,主线程是最后一个退出的线程,所以它调用pthread_exit退出将导致进程资源和进程注册的退出函数的执行; 但是,此时主线程已经无力继续执行了,所以主线程最后的一句printf语句无法被执行了。


Q4: 如果子线程是最后退出的线程,子线程是否同样可以调用注册的清理函数以及线程私有数据?

A: 由上面的理论,当然可以。下面将实例证明此过程:

  1. #define PRINT_S(str)      printf("%s\n", (#str));  
  2.   
  3. #define LOG_ENTER_FUNC      printf("enter func %s\n", __func__);  
  4.   
  5. pthread_key_t   key;  
  6.   
  7. void    exit_process_sonthread_registered()  
  8. {  
  9.     LOG_ENTER_FUNC  
  10. }  
  11.   
  12. void    exit_process()  
  13. {  
  14.     int ret;  
  15.       
  16.     LOG_ENTER_FUNC  
  17.       
  18.     ret = pthread_key_delete(key);  
  19.     if(ret != 0)  
  20.         perror("pthread_key_delete error");  
  21.     else  
  22.         PRINT_S("pthread_key_delete ok");  
  23. }  
  24.   
  25. void    cleanup_func(void *arg)  
  26. {  
  27.     LOG_ENTER_FUNC  
  28. }  
  29.   
  30. void    destruct_key_func(void *arg)  
  31. {  
  32.     LOG_ENTER_FUNC  
  33. }  
  34.   
  35. void    *thread_func(void *args)    
  36. {    
  37.     int temp = 99;  
  38.     int ret;  
  39.     pthread_cleanup_push(cleanup_func, NULL);  
  40.       
  41.     atexit(exit_process_sonthread_registered);  
  42.     sleep(2);  
  43.       
  44.     ret = pthread_setspecific(key, &temp);  // use key  
  45.     if(ret != 0)  
  46.         perror("pthread_setspecific error");  
  47.       
  48.     printf("son thread end...\n");  
  49.     pthread_exit(NULL);   
  50.       
  51.     pthread_cleanup_pop(0);  
  52. }    
  53.   
  54. // main thread  
  55. int main(int argc, char **argv)  
  56. {   
  57.     pthread_t son_thread;  
  58.     int ret;    
  59.       
  60.     atexit(exit_process);  
  61.       
  62.     ret = pthread_key_create(&key, destruct_key_func);  
  63.     RETURN_ERROR(pthread_key_create, ret, -1)   
  64.       
  65.     ret = pthread_create(&son_thread, NULL, thread_func, NULL);    
  66.     RETURN_ERROR(pthread_create, ret, -1)   
  67.     ret = pthread_detach(son_thread);  
  68.     RETURN_ERROR(pthread_detach, ret, -1)   
  69.   
  70.     printf("[Main thread]: will end...\n");  
  71.     pthread_exit(NULL);  
  72.       
  73.     return 0;      
  74. }  

上面的代码,主线程自己结束后,子线程继续执行,并且子线程有注册的清理函数,有使用线程私有数据,下面是运行结果:
[plain] view plain copy
  1. [Main thread]: will end...  
  2. son thread end...  
  3. enter func cleanup_func  
  4. enter func destruct_key_func  
  5. enter func exit_process_sonthread_registered  
  6. enter func exit_process  
  7. "pthread_key_delete ok"  

可以看出,该执行的都执行了。



Q5: 上面的过程,子线程虽然睡眠了2秒,但是如何证明主线程确实比子线程先执行完了?

A: 可以使用pthread_kill函数来得到线程的活跃状态。如下代码:

  1. pthread_t       main_thread;  
  2.   
  3. void    *thread_func(void *args)    
  4. {    
  5.     int ret;  
  6.   
  7.     sleep(2);  
  8.       
  9.     // test whether main thread is active  
  10.     ret = pthread_kill(main_thread, 0);  
  11.     if(ret == ESRCH)  
  12.         printf("main thread has quited or not exist...\n");  
  13.     else if(ret == EINVAL)  
  14.         printf("signal isn't valid...\n");  
  15.     else  
  16.         printf("main thread is active...\n");  
  17.       
  18.     printf("son thread end...\n");  
  19.     pthread_exit(NULL);   
  20. }    
  21.   
  22. // main thread  
  23. int main(int argc, char **argv)  
  24. {   
  25.     pthread_t son_thread;  
  26.     int ret;    
  27.   
  28.     main_thread = pthread_self();   // get main thread  
  29.       
  30.     ret = pthread_create(&son_thread, NULL, thread_func, NULL);    
  31.     RETURN_ERROR(pthread_create, ret, -1)   
  32.     ret = pthread_detach(son_thread);  
  33.     RETURN_ERROR(pthread_detach, ret, -1)   
  34.   
  35.     printf("[Main thread]: will end...\n");  
  36.     pthread_exit(NULL);  
  37.       
  38.     return 0;      
  39. }  

执行结果:
[plain] view plain copy
  1. [Main thread]: will end...  
  2. main thread has quited or not exist...  
  3. son thread end...  

可以看出,子线程使用pthread_kill得到主线程的状态为退出或者不存在,其实这也证明了主线程已经提前退出了。



Q6: pthread_kill函数是做什么的?

A: 不要被它的名字吓怕了,它不是专门kill线程的,它只是给线程传信号的。如果信号传递的是0,那么只执行线程是否存在的检查,就像上面的代码那样。但是,如果传入的信号不是0,而是其它信号(或者说是进程信号,如SIGINT, SIGQUIT等),那么线程将需要处理这些信号。如果线程没有注册这些信号的处理函数,那么信号将默认继续传送给进程。也就是说,如果给一个线程传递SIGQUIT信号,但是线程没有注册处理SIGQUIT信号的函数,那么SIGQUIT信号被传递到进程中,进程会结束。

下面看下pthread_kill的源代码:

  1. int     
  2. pthread_kill (  
  3.         pthread_t th,  
  4.         int sig)  
  5. {  
  6.     int error = 0;  
  7.     mach_port_t kport = MACH_PORT_NULL;  
  8.       
  9.     if ((sig < 0) || (sig > NSIG))  
  10.         return(EINVAL);  
  11.   
  12.     if (_pthread_lookup_thread(th, &kport, 0) != 0)  
  13.         return (ESRCH); /* Not a valid thread */  
  14.   
  15.     /* if the thread is a workqueue thread, just return error */  
  16.     if ((th->wqthread != 0) && (th->wqkillset ==0)) {  
  17.         return(ENOTSUP);  
  18.     }  
  19.   
  20.     error = __pthread_kill(kport, sig);  
  21.   
  22.     if (error == -1)  
  23.         error = errno;  
  24.     return(error);  
  25. }  
它开始检查了信号是否合法,并查询了线程是否合法,调用了内部函数__pthread_kill发送信号。很可惜,此函数苹果没有公开源代码。



Q7: 对于一个线程A,它创建了子线程B,A结束了,B会结束吗?

A: 可以这么说,一个线程中创建了另一个线程,此时它们没有任何依赖关系的,它们都根据操作系统的调度系统自由运行,该结束就结束,该不结束就不结束。下面有个例子:

  1. void    *grandson_thread_func(void *arg)  
  2. {  
  3.     sleep(2);  
  4.     printf("grandson_thread_func\n");  
  5.     return NULL;  
  6. }  
  7.   
  8. void    *thread_func(void *arg)  
  9. {  
  10.     pthread_t grandson_thread;  
  11.     int ret;  
  12.       
  13.     ret = pthread_create(&grandson_thread, NULL, grandson_thread_func, NULL);    
  14.     RETURN_ERROR(pthread_create, ret, 0)  
  15.     ret = pthread_detach(grandson_thread);  
  16.     RETURN_ERROR(pthread_detach, ret, 0)  
  17.       
  18.     PRINT_S("son thread will exit")  
  19.     return NULL;  
  20. }   
  21.   
  22. // main thread  
  23. int main(int argc, char **argv)  
  24. {   
  25.     pthread_t son_thread;  
  26.     int ret;    
  27.       
  28.     ret = pthread_create(&son_thread, NULL, thread_func, NULL);    
  29.     RETURN_ERROR(pthread_create, ret, -1)   
  30.       
  31.     ret = pthread_detach(son_thread);  
  32.     RETURN_ERROR(pthread_detach, ret, -1)  
  33.       
  34.     while (1)   // wait here  
  35.         ;  
  36.       
  37.     printf("[Main thread]: will end...\n");  
  38.     return 0;      
  39. }  

上面的代码中,主线程创建新线程A,新线程A创建一个新线程B,线程A结束后,如果线程B中的输出依然可以输出,表明A的结束并没有导致B的结束。

运行结果:

[plain] view plain copy
  1. "son thread will exit"  
  2. grandson_thread_func  

可以看到,线程B没有因为创建它的线程A的结束而结束。



Q8: 为什么主线程最后结束后,所有子线程不管运行不运行,都会被中断结束?

A: 这在于主线程执行函数比较特殊,它有_start模块调用,它结束后会调用exit结束进程,这个操作将强制终止进程所有子线程的运行,导致了子线程被中断。而对于子线程来说,就没有这种特殊情况。



Q9: pthread_cancel同样可以取消线程执行,为什么下面代码中的线程没有很快结束?

  1. pthread_key_t   key;  
  2. int i = 0;  
  3.   
  4. void    destructkey_func(void *arg)  
  5. {  
  6.     LOG_ENTER_FUNC  
  7. }  
  8.   
  9. void    cleanup_func(void *arg)  
  10. {  
  11.     LOG_ENTER_FUNC  
  12.     PRINT_D(i)  
  13. }  
  14.   
  15. void    *thread_func(void *arg)  
  16. {  
  17.     int sum;  
  18.     int ret;  
  19.       
  20.     pthread_cleanup_push(cleanup_func, NULL);  
  21.     ret = pthread_setspecific(key, &i);  
  22.     RETURN_ERROR(pthread_setspecific, ret, 0)  
  23.       
  24.     for(; i < INT_MAX; ++i)  
  25.         sum += i;  
  26.       
  27.     PRINT_S("son thread will exit")  
  28.     return NULL;  
  29.       
  30.     pthread_cleanup_pop(0);  
  31. }   
  32.   
  33. // main thread  
  34. int main(int argc, char **argv)  
  35. {   
  36.     pthread_t son_thread;  
  37.     int ret;    
  38.       
  39.     ret = pthread_key_create(&key, destructkey_func);  
  40.     RETURN_ERROR(pthread_key_create, ret, -1)   
  41.       
  42.     ret = pthread_create(&son_thread, NULL, thread_func, NULL);    
  43.     RETURN_ERROR(pthread_create, ret, -1)   
  44.       
  45.     // cancel the son thread  
  46.     ret = pthread_cancel(son_thread);  
  47.     RETURN_ERROR(pthread_cancel, ret, -1)   
  48.       
  49.     // wait son thread to exit  
  50.     ret = pthread_join(son_thread, NULL);  
  51.     RETURN_ERROR(pthread_join, ret, -1)   
  52.       
  53.     ret = pthread_key_delete(key);  
  54.     RETURN_ERROR(pthread_key_delete, ret, -1)  
  55.       
  56.     printf("[Main thread]: will end...\n");  
  57.     return 0;      
  58. }  

主线程创建子线程后,cancel它,但是子线程在for循环中一直无法脱身进入结束状态,这是为什么?

A: 这在于pthread_cancel只是在线程中设置了一个取消的状态,至于线程会不会进入结束状态,这取决于线程是否允许被取消,以及被取消的类型是立即还是延迟的。延迟的就是指需要进入取消点才会进入结束状态, 立即的是指尽快结束线程(但是这也不一定能保证线程一定能满足需求一样尽快结束,内部实现可能依然会有一些等待时间)。上面的代码运行:

[plain] view plain copy
  1. [xxxx seconds later]  
  2. "son thread will exit"  
  3. enter func cleanup_func  
  4. i is 2147483647  
  5. enter func destructkey_func  
  6. [Main thread]: will end...  

第一行不是运行的输出,而是指明是N秒过后,才输出后面的信息。在这里,可以看出,cancel以一个线程,线程的清理函数以及线程私有数据也是会被调用或者释放的。从这个角度来说,pthread_cancel也是一个相对不错的结束线程的方式。

在上面的那段代码中,线程的取消状态和类型没有被特定的设置,那么是采用默认的状态,那就是允许取消以及采用延迟取消的方式.延迟取消的方式就是必须线程进入取消点的时候才会进入结束。取消点在c库中并不能保证很好地实现,但是pthread库中,根据标准,pthread_join, pthread_testcancel, pthread_cond_wait, pthread_cond_timedwait, sem_wait, sigwait函数等引起阻塞的函数都是取消点。另外,read, write等引起系统阻塞的系统调用也是取消点。从此,可以看出,上面的子线程代码for循环一直处于内存数据操作,没有和上面所说的取消点有关系,所以线程会一直运行到最后的PRINT_S输出代码才引起取消点有效,线程进入结束状态。

了解了上面的原理之后,就可以让线程尽可能快地随着pthread_cancel调用而结束了,如下:

  1. void    *thread_func(void *arg)  
  2. {  
  3.     int sum;  
  4.     int ret;  
  5.       
  6.     pthread_cleanup_push(cleanup_func, NULL);  
  7.     ret = pthread_setspecific(key, &i);  
  8.     RETURN_ERROR(pthread_setspecific, ret, 0)  
  9.       
  10.     for(; i < INT_MAX; ++i)  
  11.     {  
  12.         // if cancel mark exists, then will exit the thread as soon as possible  
  13.         pthread_testcancel();     
  14.         sum += i;  
  15.     }  
  16.       
  17.     PRINT_S("son thread will exit")  
  18.     return NULL;  
  19.       
  20.     pthread_cleanup_pop(0);  
  21. }   

上面对子线程代码的修改为for循环中加入了pthread_testcancel函数,它会判断是否取消标志被标示,如果被标示,那么线程将立即准备结束。如下运行结果:
[plain] view plain copy
  1. enter func cleanup_func  
  2. i is 0  
  3. enter func destructkey_func  
  4. [Main thread]: will end...  

可以看出,for循环最多只跑1遍,线程就结束了;可见pthread_testcancel作用之大!



Q10: 如果采用设置线程取消类型为PTHREAD_CANCEL_ASYNCHRONOUS的方式来尽快可以结束线程,这样线程一定会立即结束吗?

A: 这个不一定。设置这个标志位只保证了会尽快来结束线程,但是调度策略不同,以及线程执行情况不同,都无法保证。如下:

  1. void    *thread_func(void *arg)  
  2. {  
  3.     int sum;  
  4.     int ret;  
  5.     int old_type;  
  6.       
  7.     pthread_cleanup_push(cleanup_func, NULL);  
  8.     ret = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);  
  9.     RETURN_ERROR(pthread_setcanceltype, ret, 0)  
  10.       
  11.     ret = pthread_setspecific(key, &i);  
  12.     RETURN_ERROR(pthread_setspecific, ret, 0)  
  13.       
  14.     for(; i < INT_MAX; ++i)  
  15.     {    
  16.         sum += i;  
  17.     }  
  18.       
  19.     PRINT_S("son thread will exit")  
  20.     return NULL;  
  21.       
  22.     pthread_cleanup_pop(0);  
  23. }   
  24.   
  25. // main thread  
  26. int main(int argc, char **argv)  
  27. {   
  28.     pthread_t son_thread;  
  29.     int ret;    
  30.       
  31.     ret = pthread_key_create(&key, destructkey_func);  
  32.     RETURN_ERROR(pthread_key_create, ret, -1)   
  33.       
  34.     ret = pthread_create(&son_thread, NULL, thread_func, NULL);    
  35.     RETURN_ERROR(pthread_create, ret, -1)   
  36.       
  37.     sleep(1);  
  38.       
  39.     // cancel the son thread  
  40.     ret = pthread_cancel(son_thread);  
  41.     RETURN_ERROR(pthread_cancel, ret, -1)   
  42.       
  43.     PRINT_S("has call cancel the son thread:")  
  44.       
  45.     // wait son thread to exit  
  46.     ret = pthread_join(son_thread, NULL);  
  47.     RETURN_ERROR(pthread_join, ret, -1)   
  48.       
  49.     ret = pthread_key_delete(key);  
  50.     RETURN_ERROR(pthread_key_delete, ret, -1)  
  51.       
  52.     printf("[Main thread]: will end...\n");  
  53.     return 0;      
  54. }  

运行结果:
[plain] view plain copy
  1. "has call cancel the son thread:"  
  2. [xxxxx seconds later]  
  3. "son thread will exit"  
  4. enter func cleanup_func  
  5. i is 2147483647  
  6. enter func destructkey_func  
  7. [Main thread]: will end...  

可以看出,线程还是等待for循环结束才结束。



Q11: 对于pthread_cancel函数,它内部是如何实现的?

A: 如下代码:

  1. int  
  2. pthread_cancel(pthread_t thread)  
  3. {  
  4. #if __DARWIN_UNIX03  
  5.     if (__unix_conforming == 0)  
  6.         __unix_conforming = 1;  
  7. #endif /* __DARWIN_UNIX03 */  
  8.   
  9.     if (_pthread_lookup_thread(thread, NULL, 0) != 0)  
  10.         return(ESRCH);  
  11.   
  12.     /* if the thread is a workqueue thread, then return error */  
  13.     if (thread->wqthread != 0) {  
  14.         return(ENOTSUP);  
  15.     }  
  16. #if __DARWIN_UNIX03  
  17.     int state;  
  18.   
  19.     LOCK(thread->lock);  
  20.     state = thread->cancel_state |= _PTHREAD_CANCEL_PENDING;  
  21.     UNLOCK(thread->lock);  
  22.     if (state & PTHREAD_CANCEL_ENABLE)  
  23.         __pthread_markcancel(thread->kernel_thread);   // do cancel mark   
  24. #else /* __DARWIN_UNIX03 */  
  25.     thread->cancel_state |= _PTHREAD_CANCEL_PENDING;  
  26. #endif /* __DARWIN_UNIX03 */  
  27.     return (0);  
  28. }  

可以很清楚地看到,它内部设置了cancel标志位。对于__pthread_markcancel函数,苹果没有公开代码,只有一个原型:
  1. extern int __pthread_markcancel(int);  

另外,我们看下pthread_setcancelstate的源代码:
  1. /* 
  2.  * Query/update the cancelability 'state' of a thread 
  3.  */  
  4. int  
  5. pthread_setcancelstate(int state, int *oldstate)  
  6. {  
  7. #if __DARWIN_UNIX03  
  8.     if (__unix_conforming == 0) {  
  9.         __unix_conforming = 1;  
  10.     }  
  11.     return (_pthread_setcancelstate_internal(state, oldstate, 1));  
  12. #else /* __DARWIN_UNIX03 */  
  13.     return (_pthread_setcancelstate_internal(state, oldstate, 0));  
  14. #endif /* __DARWIN_UNIX03 */  
  15.   
  16. }  
_pthread_setcancelstate_internal代码如下:
  1. /* 
  2.  * Query/update the cancelability 'state' of a thread 
  3.  */  
  4. int  
  5. _pthread_setcancelstate_internal(int state, int *oldstate, int conforming)  
  6. {  
  7.     pthread_t self = pthread_self();  
  8.   
  9.   
  10.     switch (state) {  
  11.         case PTHREAD_CANCEL_ENABLE:  
  12.         if (conforming)  
  13.             __pthread_canceled(1);  
  14.             break;  
  15.         case PTHREAD_CANCEL_DISABLE:  
  16.         if (conforming)  
  17.             __pthread_canceled(2);  
  18.             break;  
  19.         default:  
  20.             return EINVAL;  
  21.     }  
  22.   
  23.     self = pthread_self();  
  24.     LOCK(self->lock);  
  25.     if (oldstate)  
  26.         *oldstate = self->cancel_state & _PTHREAD_CANCEL_STATE_MASK;  
  27.     self->cancel_state &= ~_PTHREAD_CANCEL_STATE_MASK;  
  28.     self->cancel_state |= state;  
  29.     UNLOCK(self->lock);  
  30.     if (!conforming)  
  31.         _pthread_testcancel(self, 0);  /* See if we need to 'die' now... */  
  32.     return (0);  
  33. }  

它的主要作用是设置cancel_state标志位; 同时,它对于是否允许取消线程也做了不同的处理。

对于pthread_setcanceltype函数,代码如下:

  1. /* 
  2.  * Query/update the cancelability 'type' of a thread 
  3.  */  
  4. int  
  5. pthread_setcanceltype(int type, int *oldtype)  
  6. {  
  7.     pthread_t self = pthread_self();  
  8.       
  9. #if __DARWIN_UNIX03  
  10.     if (__unix_conforming == 0)  
  11.         __unix_conforming = 1;  
  12. #endif /* __DARWIN_UNIX03 */  
  13.   
  14.     if ((type != PTHREAD_CANCEL_DEFERRED) &&   
  15.         (type != PTHREAD_CANCEL_ASYNCHRONOUS))  
  16.         return EINVAL;  
  17.     self = pthread_self();  
  18.     LOCK(self->lock);  
  19.     if (oldtype)  
  20.         *oldtype = self->cancel_state & _PTHREAD_CANCEL_TYPE_MASK;  
  21.     self->cancel_state &= ~_PTHREAD_CANCEL_TYPE_MASK;  
  22.     self->cancel_state |= type;  
  23.     UNLOCK(self->lock);  
  24. #if !__DARWIN_UNIX03  
  25.     _pthread_testcancel(self, 0);  /* See if we need to 'die' now... */  
  26. #endif /* __DARWIN_UNIX03 */  
  27.     return (0);  
  28. }  
它主要做了设置cancel_state标志位的事情。


pthread_testcancel函数做了如下:

  1. void  
  2. pthread_testcancel(void)  
  3. {  
  4.     pthread_t self = pthread_self();  
  5.   
  6. #if __DARWIN_UNIX03  
  7.     if (__unix_conforming == 0)  
  8.         __unix_conforming = 1;  
  9.     _pthread_testcancel(self, 1);  
  10. #else /* __DARWIN_UNIX03 */  
  11.     _pthread_testcancel(self, 0);  
  12. #endif /* __DARWIN_UNIX03 */  
  13.   
  14. }  
内部调用的函数_pthread_testcancel代码如下:
  1. /* 
  2.  * Insert a cancellation point in a thread. 
  3.  */  
  4. __private_extern__ void  
  5. _pthread_testcancel(pthread_t threadint isconforming)  
  6. {  
  7.     LOCK(thread->lock);  
  8.     if ((thread->cancel_state & (PTHREAD_CANCEL_ENABLE|_PTHREAD_CANCEL_PENDING)) ==   
  9.         (PTHREAD_CANCEL_ENABLE|_PTHREAD_CANCEL_PENDING))  
  10.     {  
  11.         UNLOCK(thread->lock);  
  12.         if (isconforming)  
  13.             pthread_exit(PTHREAD_CANCELED);  
  14.         else  
  15.             pthread_exit(0);  
  16.     }  
  17.     UNLOCK(thread->lock);  
  18. }  

可以看出,它在必要的时刻,调用了pthread_exit来结束线程。


最后,顺便贴下pthread_create的源代码(因为调用函数太多,只贴出前2个级别调用):

  1. int  
  2. pthread_create(pthread_t *thread,  
  3.            const pthread_attr_t *attr,  
  4.            void *(*start_routine)(void *),  
  5.            void *arg)  
  6. {  
  7.     return _new_pthread_create_suspended(thread, attr, start_routine, arg, 0);  
  8. }  

内部函数调用:
  1. static int         
  2. _new_pthread_create_suspended(pthread_t *thread,   
  3.            const pthread_attr_t *attr,  
  4.            void *(*start_routine)(void *),   
  5.            void *arg,  
  6.         int create_susp)  
  7. {  
  8.     pthread_attr_t *attrs;  
  9.     void *stack;  
  10.     int error;  
  11.     unsigned int flags;  
  12.     pthread_t t,t2;  
  13.     kern_return_t kern_res;  
  14.     mach_port_t kernel_thread = MACH_PORT_NULL;  
  15.     int needresume;  
  16.     task_t self = mach_task_self();  
  17.     int kernalloc = 0;  
  18.     int susp = create_susp;  
  19.   
  20.     if ((attrs = (pthread_attr_t *)attr) == (pthread_attr_t *)NULL)  
  21.     {           /* Set up default paramters */  
  22.         attrs = &_pthread_attr_default;  
  23.     } else if (attrs->sig != _PTHREAD_ATTR_SIG) {  
  24.             return EINVAL;  
  25.     }  
  26.     error = 0;  
  27.   
  28.     if (((attrs->policy != _PTHREAD_DEFAULT_POLICY)  ||   
  29.         (attrs->param.sched_priority != default_priority)) && (create_susp == 0)) {  
  30.         needresume = 1;   
  31.         susp = 1;     
  32.     } else   
  33.         needresume = 0;  
  34.   
  35.     /* In default policy (ie SCHED_OTHER) only sched_priority is used. Check for 
  36.      * any change in priority or policy is needed here. 
  37.      */  
  38.     if ((__oldstyle == 1) || (create_susp != 0)) {  
  39.         /* Rosetta or pthread_create_suspended() */  
  40.         /* running under rosetta */  
  41.         /* Allocate a stack for the thread */  
  42. #if PTH_TRACE  
  43.         __kdebug_trace(0x9000000, create_susp, 0, 0, 0, 0);  
  44. #endif  
  45.                 if ((error = _pthread_allocate_stack(attrs, &stack)) != 0) {  
  46.             return(error);  
  47.                 }  
  48.         t = (pthread_t)malloc(sizeof(struct _pthread));  
  49.         *thread = t;  
  50.         if (susp) {  
  51.             /* Create the Mach thread for this thread */  
  52.             PTHREAD_MACH_CALL(thread_create(self, &kernel_thread), kern_res);  
  53.             if (kern_res != KERN_SUCCESS)  
  54.             {  
  55.                 printf("Can't create thread: %d\n", kern_res);  
  56.                 return(EINVAL);  
  57.             }  
  58.         }  
  59.                 if ((error = _pthread_create(t, attrs, stack, kernel_thread)) != 0)  
  60.         {  
  61.             return(error);  
  62.         }  
  63.         set_malloc_singlethreaded(0);  
  64.         __is_threaded = 1;  
  65.   
  66.         /* Send it on it's way */  
  67.         t->arg = arg;  
  68.         t->fun = start_routine;  
  69.         t->newstyle = 0;  
  70.         /* Now set it up to execute */  
  71.         LOCK(_pthread_list_lock);  
  72.         TAILQ_INSERT_TAIL(&__pthread_head, t, plist);  
  73. #if PTH_LISTTRACE  
  74.         __kdebug_trace(0x900000c, t, 0, 0, 4, 0);  
  75. #endif  
  76.         _pthread_count++;  
  77.         UNLOCK(_pthread_list_lock);  
  78.         _pthread_setup(t, _pthread_body, stack, susp, needresume);  
  79.         return(0);  
  80.     } else {  
  81.   
  82.         flags = 0;  
  83.         if (attrs->fastpath == 1)  
  84.             kernalloc = 1;  
  85.   
  86.         if (attrs->detached == PTHREAD_CREATE_DETACHED)  
  87.                 flags |= PTHREAD_START_DETACHED;  
  88.         if (attrs->schedset != 0) {  
  89.             flags |= PTHREAD_START_SETSCHED;  
  90.             flags |= ((attrs->policy & PTHREAD_START_POLICY_MASK) << PTHREAD_START_POLICY_BITSHIFT);  
  91.             flags |= (attrs->param.sched_priority & PTHREAD_START_IMPORTANCE_MASK);  
  92.         }   
  93.   
  94.         set_malloc_singlethreaded(0);  
  95.         __is_threaded = 1;  
  96.   
  97.         if (kernalloc == 0) {  
  98.             /* Allocate a stack for the thread */  
  99.             flags |= PTHREAD_START_CUSTOM;  
  100.             if ((error = _pthread_create_pthread_onstack(attrs, &stack, &t)) != 0) {  
  101.                 return(error);  
  102.             }  
  103.             /* Send it on it's way */  
  104.             t->arg = arg;  
  105.             t->fun = start_routine;  
  106.             t->newstyle = 1;  
  107.   
  108. #if PTH_TRACE  
  109.         __kdebug_trace(0x9000004, t, flags, 0, 0, 0);  
  110. #endif  
  111.               
  112.             if ((t2 = __bsdthread_create(start_routine, arg, stack, t, flags)) == (pthread_t)-1) {  
  113.                 _pthread_free_pthread_onstack(t, 1, 0);  
  114.                 return (EAGAIN);  
  115.             }  
  116.             else t=t2;  
  117.             LOCK(_pthread_list_lock);  
  118.             t->parentcheck = 1;  
  119.             if ((t->childexit != 0) && ((t->detached & PTHREAD_CREATE_DETACHED) == PTHREAD_CREATE_DETACHED)) {  
  120.                 /* detached child exited, mop up */  
  121.                 UNLOCK(_pthread_list_lock);  
  122. #if PTH_TRACE  
  123.             __kdebug_trace(0x9000008, t, 0, 0, 1, 0);  
  124. #endif  
  125.             if(t->freeStackOnExit)  
  126.                                 vm_deallocate(self, (mach_vm_address_t)(uintptr_t)t, pthreadsize);  
  127.                         else  
  128.                 free(t);  
  129.             } else if (t->childrun == 0) {  
  130.                 TAILQ_INSERT_TAIL(&__pthread_head, t, plist);  
  131.                 _pthread_count++;  
  132. #if PTH_LISTTRACE  
  133.                 __kdebug_trace(0x900000c, t, 0, 0, 1, 0);  
  134. #endif  
  135.                 UNLOCK(_pthread_list_lock);  
  136.             } else   
  137.                 UNLOCK(_pthread_list_lock);  
  138.   
  139.             *thread = t;  
  140.   
  141. #if PTH_TRACE  
  142.         __kdebug_trace(0x9000014, t, 0, 0, 1, 0);  
  143. #endif  
  144.             return (0);  
  145.   
  146.         } else {  
  147.             /* kernel allocation */  
  148. #if PTH_TRACE  
  149.         __kdebug_trace(0x9000018, flags, 0, 0, 0, 0);  
  150. #endif  
  151.             if ((t = __bsdthread_create(start_routine, arg, (void *)attrs->stacksize, NULL, flags)) == (pthread_t)-1)  
  152.                 return (EAGAIN);  
  153.             /* Now set it up to execute */  
  154.             LOCK(_pthread_list_lock);  
  155.             t->parentcheck = 1;  
  156.             if ((t->childexit != 0) && ((t->detached & PTHREAD_CREATE_DETACHED) == PTHREAD_CREATE_DETACHED)) {  
  157.                 /* detached child exited, mop up */  
  158.                 UNLOCK(_pthread_list_lock);  
  159. #if PTH_TRACE  
  160.             __kdebug_trace(0x9000008, t, pthreadsize, 0, 2, 0);  
  161. #endif  
  162.                 vm_deallocate(self, (mach_vm_address_t)(uintptr_t)t, pthreadsize);  
  163.             } else if (t->childrun == 0) {  
  164.                 TAILQ_INSERT_TAIL(&__pthread_head, t, plist);  
  165.                 _pthread_count++;  
  166. #if PTH_LISTTRACE  
  167.                 __kdebug_trace(0x900000c, t, 0, 0, 2, 0);  
  168. #endif  
  169.                 UNLOCK(_pthread_list_lock);  
  170.             } else   
  171.                 UNLOCK(_pthread_list_lock);  
  172.   
  173.             *thread = t;  
  174.   
  175. #if PTH_TRACE  
  176.             __kdebug_trace(0x9000014, t, 0, 0, 2, 0);  
  177. #endif  
  178.             return(0);  
  179.         }  
  180.     }  
  181. }  

另外,贴下pthread_t结构:
  1. typedef struct _pthread  
  2. {  
  3.     long           sig;       /* Unique signature for this structure */  
  4.     struct __darwin_pthread_handler_rec *__cleanup_stack;  
  5.     pthread_lock_t lock;          /* Used for internal mutex on structure */  
  6.     uint32_t    detached:8,  
  7.             inherit:8,  
  8.             policy:8,  
  9.             freeStackOnExit:1,  
  10.             newstyle:1,  
  11.             kernalloc:1,  
  12.             schedset:1,  
  13.             wqthread:1,  
  14.             wqkillset:1,  
  15.             pad:2;  
  16.     size_t         guardsize;   /* size in bytes to guard stack overflow */  
  17. #if  !defined(__LP64__)  
  18.     int        pad0;        /* for backwards compatibility */  
  19. #endif  
  20.     struct sched_param param;  
  21.     uint32_t    cancel_error;  
  22. #if defined(__LP64__)  
  23.     uint32_t    cancel_pad; /* pad value for alignment */  
  24. #endif  
  25.     struct _pthread *joiner;  
  26. #if !defined(__LP64__)  
  27.     int     pad1;       /* for backwards compatibility */  
  28. #endif  
  29.     void           *exit_value;  
  30.     semaphore_t    death;       /* pthread_join() uses this to wait for death's call */  
  31.     mach_port_t    kernel_thread; /* kernel thread this thread is bound to */  
  32.     void           *(*fun)(void*);/* Thread start routine */  
  33.         void           *arg;          /* Argment for thread start routine */  
  34.     int        cancel_state;  /* Whether thread can be cancelled */  
  35.     int        err_no;      /* thread-local errno */  
  36.     void           *tsd[_EXTERNAL_POSIX_THREAD_KEYS_MAX + _INTERNAL_POSIX_THREAD_KEYS_MAX];  /* Thread specific data */  
  37.         void           *stackaddr;     /* Base of the stack (is aligned on vm_page_size boundary */  
  38.         size_t         stacksize;      /* Size of the stack (is a multiple of vm_page_size and >= PTHREAD_STACK_MIN) */  
  39.     mach_port_t    reply_port;     /* Cached MiG reply port */  
  40. #if defined(__LP64__)  
  41.         int     pad2;       /* for natural alignment */  
  42. #endif  
  43.     void           *cthread_self;  /* cthread_self() if somebody calls cthread_set_self() */  
  44.     /* protected by list lock */  
  45.     uint32_t    childrun:1,  
  46.             parentcheck:1,  
  47.             childexit:1,  
  48.             pad3:29;  
  49. #if defined(__LP64__)  
  50.     int     pad4;       /* for natural alignment */  
  51. #endif  
  52.     TAILQ_ENTRY(_pthread) plist;  
  53.     void *  freeaddr;  
  54.     size_t  freesize;  
  55.     mach_port_t joiner_notify;  
  56.     char    pthread_name[MAXTHREADNAMESIZE];        /* including nulll the name */  
  57.         int max_tsd_key;  
  58.     void *  cur_workq;  
  59.     void * cur_workitem;  
  60.     uint64_t thread_id;  
  61. } *pthread_t;  


总之,线程退出不是什么多么可怕的东西,操作系统大量用线程技术,上层的软件也不应该对此如此惧怕。

pthread_exit和pthread_cancel都是不错的选择; 不过,需要注意清理函数、资源释放的正确,以减少死锁问题的产生,减少线程意外崩溃问题的产生。



作者:陈曦

日期:2012-8-5  16:13:36

环境:[Mac 10.7.1 Lion Intel i3 支持64位指令 gcc4.2.1 xcode4.2 苹果开源代码Libc-763.11] 

转载请注明出处

每日总结:  优秀的架构都是类似的,垃圾的架构一般都是一个原因:代码内部原理掌握得不够


FROM: http://blog.csdn.net/cxsjabcabc/article/details/7829514

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

线程问题的核心: 怎么退出线程才是合适的----小话多线程(2) 的相关文章

随机推荐

  • rosbag commandline常用命令之filter

    可以实现对已有rosbag文件中的某些topic去除或者保留 rosbag filter input bag output bag topic tf or topic tf and m transforms 0 header frame i
  • 详解.NET实现OAuth2.0四种模式(6)RefreshToken

    我们知道 为了安全起见 AccessToken的有效时间一般是比较短的 如30分钟 在AccessToken超出有效期之后 它就不能再用于访问资源 必须重新获得 但如果每30分钟 就要让用户登录一次 那必定会让用户感到厌烦 于是就产生了Re
  • 公共命名空间,于2022年底

    公共命名空间的想法出现自2019年 到现在有三年了 在2022年底 总结一下这三年来的想法 就像字符集 字体 公共命名空间 新编译原理也是这么一对儿 字符集用来收集所有符号 字体用来显示字符集中的符号 公共命名空间用来收集所有的句子 新编译
  • java里的输入与输出

    一 概述 输入输出可以说是计算机的基本功能 作为一种语言体系 java中主要按照流 stream 的模式来实现 其中数据的流向是按照计算机的方向确定的 流入计算机的数据流叫做输入流 inputStream 由计算机发出的数据流叫做输出流 o
  • 想了很久的算法

    文章目录 1 求字符串中不重复的最长子串 2 斐波那契数列多种实现方式 1 求字符串中不重复的最长子串 var lengthOfLongestSubstring function s let setArr new Set result ma
  • 贺中国信通院“星火·链网”数字原生资产(DNA)服务网络隆重发布

    5月20日 中国信通院 星火 链网 数字原生资产 DNA 服务网络发布会在云端圆满举办 中国信通院院长 中关村区块链产业联盟理事长余晓晖出席会议并为 星火 链网 数字原生资产 DNA 服务网络上线发表寄语 中国信通院总工程师敖立 新华网首席
  • 识别图像模板旋转角度_基于视觉的焊缝识别与定位技术

    为了实现焊前引导 必须首先通过视觉传感系统识别工件和焊缝 确定焊接的关键点位置 建立关键点的二维或三维坐标 发送给机器人 将机器人的末端执行器运动到焊接起始点 自动完成焊前导引 焊缝识别的准确率与识别精度直接影响焊缝跟踪的精度 因此 焊缝识
  • 通过nginx代理拦截请求,进行全局访问限制

    声明 本博文用于学习总结及工作心得 运行环境 Ubantu 14 0 tomcat7 nginx 1 4 6 更新后1 5 6 项目中经常会用到权限管理 必然的就会存在权限的设定和验证 对于登陆或者模块的权限设定验证 在项目中直接实现 那么
  • 地图服务标注显示乱码问题

    版本 ArcGIS 10 1 在Catalog中发布了一个地图服务 直接切了图 切图后发现标注有乱码 操作系统是win7 不会涉及Server对字体库的访问权限问题 排查了一下 发现了原因 标注字体不能使用不支持中文的英文或者其他非中文字体
  • Golang基础 变量与常量

    Golang基础 变量与常量 01 变量声明 02 常量声明 03 变量初始化 04 常量初始化 参考资料 01 变量声明 变量就是内存堆栈区的一块地址空间用于存储数据 Go语言在使用变量时需要先声明变量 常用的声明方式有两种 使用var关
  • 用python最新版本安装web3后调试错误原因和解决方法

    由于调试web3 安装了最新版本的python3 11 用命令安装 pip install web3 提示安装错误 无法完成 仔细观察根据错误提示发现是 VC 14没有安装的原因 根据提示从微软官方下载vs BuildTools并单独安装V
  • 闭包(闭包使用场景,闭包内存泄漏,js内存管理及垃圾回收)

    1 什么是闭包 在认识闭包之前 我们先简单了解两个知识点 JavaScript 中的作用域和作用域链 JavaScript 中的垃圾回收 目的就是为了方便我们更容易理解闭包 1 JavaScript 中的作用域和作用域链 作用域就是一个独立
  • 内存泄漏全解析,从此拒绝ANR,让OOM远离你的身边,跟内存泄漏say byebye

    http www cnblogs com liushilin p 5900089 html 一 写在前面 二 一些杂谈 1 这里先安利一下java的内存分配 2 四种引用类型的介绍 3 内存抖动 这样的图很熟悉有木有 当这样的时候 说明你的
  • [医学多模态融合系列 -1] A review: Deep learning for medical image segmentation using multi-modality fusion 解读

    医学多模态融合系列 1 A review Deep learning for medical image segmentation using multi modality fusion 0 Abstract 1 Introduction
  • redis漏洞修复:CVE-2022-35977、CVE-2023-22458、CVE-2023-28856

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 漏洞内容 二 现状 三 更新redis 下载镜像 停止已有的容器 启动新的容器 四 更新后的版本 1 查看日志 2 查看版本 总结 前言 漏扫发现机器上的
  • MYSQL原理、设计与应用

    概述 数据库 Database DB 是按照数据结构来组织 存储和管理数据的仓库 其本身可被看作电子化的文件柜 用户可以对文件中的数据进行增删改查等操作 数据库系统是指在计算机系统中引入数据库后的系统 除了数据库 还包括数据库管理系统 Da
  • 攻防世界-MISC之如来十三掌

    一 下载打开附件1 出现一堆梵文 夜哆悉諳多苦奢陀奢諦冥神哆盧穆皤三侄三即諸諳即冥迦冥隸數顛耶迦奢若吉怯陀諳怖奢智侄諸若奢數菩奢集遠俱老竟寫明奢若梵等盧皤豆蒙密離怯婆皤礙他哆提哆多缽以南哆心曰姪罰蒙呐神 舍切真怯勝呐得俱沙罰娑是怯遠得呐數罰
  • 行人属性识别:HydraPlus-Net: Attentive Deep Features for Pedestrian Analysis

    参考文献 https arxiv org abs 1709 09930 代码实现 https github com xh liu HydraPlus Net 包括理解 HydraPlus Net Attentive Deep Feature
  • 小白学GAN系列4——torch.optim

    torch optim是一个实现了多种优化算法的包 大多数通用的方法都已支持 提供了丰富的接口调用 未来更多精炼的优化算法也将整合进来 为了使用torch optim 需先构造一个优化器对象Optimizer 用来保存当前的状态 并能够根据
  • 线程问题的核心: 怎么退出线程才是合适的----小话多线程(2)

    作者 陈曦 日期 2012 8 5 16 13 36 环境 Mac 10 7 1 Lion Intel i3 支持64位指令 gcc4 2 1 xcode4 2 苹果开源代码Libc 763 11 转载请注明出处 每日总结 优秀的架构都是类