作者:陈曦
日期: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)是直接返回给系统。但是,前者返回给什么函数?写如下代码:
int main(int argc, char **argv)
{
return 0;
}
#include <stdlib.h>
int main(int argc, char **argv)
{
exit(0);
}
分别编译成可执行文件。使用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的实现代码:
void
pthread_exit(void *value_ptr)
{
pthread_t self = pthread_self();
/* if the current thread is a workqueue thread, just crash the app, as per libdispatch folks */
if (self->wqthread == 0) {
_pthread_exit(self, value_ptr);
} else {
LIBC_ABORT("pthread_exit() may only be called against threads created via pthread_create()");
}
}
进入主要调用部分_pthread_exit函数:
static void
_pthread_exit(pthread_t self, void *value_ptr)
{
struct __darwin_pthread_handler_rec *handler;
kern_return_t kern_res;
int thread_count;
int newstyle = self->newstyle;
/* Make this thread not to receive any signals */
__disable_threadsignal(1);
#if PTH_TRACE
__kdebug_trace(0x900001c, self, newstyle, 0, 0, 0);
#endif
/* set cancel state to disable and type to deferred */
_pthread_setcancelstate_exit(self, value_ptr, __unix_conforming);
while ((handler = self->__cleanup_stack) != 0)
{
(handler->__routine)(handler->__arg); // call cleanup handler
self->__cleanup_stack = handler->__next;
}
_pthread_tsd_cleanup(self); // Clean up thread specific data
if (newstyle == 0) {
_pthread_reap_threads();
LOCK(self->lock);
self->detached |= _PTHREAD_EXITED;
if (self->detached & PTHREAD_CREATE_JOINABLE) {
mach_port_t death = self->death;
self->exit_value = value_ptr;
UNLOCK(self->lock);
/* the joiner will need a kernel thread reference, leave ours for it */
if (death) {
PTHREAD_MACH_CALL(semaphore_signal(death), kern_res);
if (kern_res != KERN_SUCCESS)
fprintf(stderr,
"semaphore_signal(death) failed: %s\n",
mach_error_string(kern_res));
}
LOCK(_pthread_list_lock);
thread_count = --_pthread_count;
UNLOCK(_pthread_list_lock);
} else {
UNLOCK(self->lock);
LOCK(_pthread_list_lock);
TAILQ_REMOVE(&__pthread_head, self, plist);
#if PTH_LISTTRACE
__kdebug_trace(0x9000010, self, 0, 0, 5, 0);
#endif
thread_count = --_pthread_count;
UNLOCK(_pthread_list_lock);
/* with no joiner, we let become available consume our cached ref */
_pthread_become_available(self, self->kernel_thread);
}
if (thread_count <= 0) // if the thread is the last one, then exit the process
exit(0);
/* Use a new reference to terminate ourselves. Should never return. */
// internal terminate thread
PTHREAD_MACH_CALL(thread_terminate(mach_thread_self()), kern_res);
fprintf(stderr, "thread_terminate(mach_thread_self()) failed: %s\n",
mach_error_string(kern_res));
} else { // newstyle != 0
semaphore_t joinsem = SEMAPHORE_NULL;
if ((self->joiner_notify == (mach_port_t)0) && (self->detached & PTHREAD_CREATE_JOINABLE))
joinsem = new_sem_from_pool();
LOCK(self->lock);
self->detached |= _PTHREAD_EXITED;
self->exit_value = value_ptr;
if (self->detached & PTHREAD_CREATE_JOINABLE) {
if (self->joiner_notify == (mach_port_t)0) {
self->joiner_notify = joinsem;
joinsem = SEMAPHORE_NULL;
}
UNLOCK(self->lock);
if (joinsem != SEMAPHORE_NULL)
restore_sem_to_pool(joinsem);
_pthread_free_pthread_onstack(self, 0, 1);
} else {
UNLOCK(self->lock);
/* with no joiner, we let become available consume our cached ref */
if (joinsem != SEMAPHORE_NULL)
restore_sem_to_pool(joinsem);
_pthread_free_pthread_onstack(self, 1, 1);
}
}
LIBC_ABORT("thread %p didn't exit", self);
}
为了更直观,我在上面的代码中加入了部分注释。从这里可以看出,pthread_exit做或不做如下几件事情:
1、已经注册的清理事件被执行;
2、线程私有数据被清理;
3、如果此线程是最后一个线程,那么会调用exit(0)退出进程。
4、不会释放进程相关的资源(如文件句柄、互斥体、内存等), 也不会执行进程注册的退出函数(如atexit注册的函数); 如果是最后一个线程,那么调用exit(0)将会做释放进程资源和执行进程注册的退出函数的事情。
由上面描述,pthread_exit还是一个比较安全的退出函数,但是主线程使用的话不太推荐,子线程使用还是没问题的。
Q3: 举个子线程跑完,主线程最后执行调用pthread_exit,而能调用进程资源释放的例子吧。
A:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#define PRINT_D(intValue) printf(#intValue" is %d\n", (intValue));
#define PRINT_U(uintValue) printf(#uintValue" is %lu\n", (uintValue));
#define PRINT_STR(str) printf(#str" is %s\n", (str));
#define PRINT_S(str) printf("%s\n", (#str));
#define FOREVER_PRINT { while(1) printf("...");}
#define RETURN_ERROR(func, ret, return_value) \
if((ret) != 0) \
{ \
perror(#func" error"); \
printf("ret is %d\n", (ret)); \
return (return_value); \
}
void *thread_func(void *args)
{
printf("son thread end...\n");
return NULL;
}
void exit_process()
{
PRINT_S("exit_process is called...")
}
// main thread
int main(int argc, char **argv)
{
pthread_t son_thread;
int ret;
atexit(exit_process);
ret = pthread_create(&son_thread, NULL, thread_func, NULL);
RETURN_ERROR(pthread_create, ret, -1)
ret = pthread_detach(son_thread);
RETURN_ERROR(pthread_detach, ret, -1)
sleep(2);
pthread_exit(NULL);
printf("[Main thread]: end...\n");
return 0;
}
主线程创建子线程后,睡眠2秒等待子线程完成,然后调用pthread_exit退出主线程。
执行结果:
son thread end...
"exit_process is called..."
进程注册的exit_process函数被执行了,这是因为子线程正常结束了,主线程是最后一个退出的线程,所以它调用pthread_exit退出将导致进程资源和进程注册的退出函数的执行; 但是,此时主线程已经无力继续执行了,所以主线程最后的一句printf语句无法被执行了。
Q4: 如果子线程是最后退出的线程,子线程是否同样可以调用注册的清理函数以及线程私有数据?
A: 由上面的理论,当然可以。下面将实例证明此过程:
#define PRINT_S(str) printf("%s\n", (#str));
#define LOG_ENTER_FUNC printf("enter func %s\n", __func__);
pthread_key_t key;
void exit_process_sonthread_registered()
{
LOG_ENTER_FUNC
}
void exit_process()
{
int ret;
LOG_ENTER_FUNC
ret = pthread_key_delete(key);
if(ret != 0)
perror("pthread_key_delete error");
else
PRINT_S("pthread_key_delete ok");
}
void cleanup_func(void *arg)
{
LOG_ENTER_FUNC
}
void destruct_key_func(void *arg)
{
LOG_ENTER_FUNC
}
void *thread_func(void *args)
{
int temp = 99;
int ret;
pthread_cleanup_push(cleanup_func, NULL);
atexit(exit_process_sonthread_registered);
sleep(2);
ret = pthread_setspecific(key, &temp); // use key
if(ret != 0)
perror("pthread_setspecific error");
printf("son thread end...\n");
pthread_exit(NULL);
pthread_cleanup_pop(0);
}
// main thread
int main(int argc, char **argv)
{
pthread_t son_thread;
int ret;
atexit(exit_process);
ret = pthread_key_create(&key, destruct_key_func);
RETURN_ERROR(pthread_key_create, ret, -1)
ret = pthread_create(&son_thread, NULL, thread_func, NULL);
RETURN_ERROR(pthread_create, ret, -1)
ret = pthread_detach(son_thread);
RETURN_ERROR(pthread_detach, ret, -1)
printf("[Main thread]: will end...\n");
pthread_exit(NULL);
return 0;
}
上面的代码,主线程自己结束后,子线程继续执行,并且子线程有注册的清理函数,有使用线程私有数据,下面是运行结果:
[Main thread]: will end...
son thread end...
enter func cleanup_func
enter func destruct_key_func
enter func exit_process_sonthread_registered
enter func exit_process
"pthread_key_delete ok"
可以看出,该执行的都执行了。
Q5: 上面的过程,子线程虽然睡眠了2秒,但是如何证明主线程确实比子线程先执行完了?
A: 可以使用pthread_kill函数来得到线程的活跃状态。如下代码:
pthread_t main_thread;
void *thread_func(void *args)
{
int ret;
sleep(2);
// test whether main thread is active
ret = pthread_kill(main_thread, 0);
if(ret == ESRCH)
printf("main thread has quited or not exist...\n");
else if(ret == EINVAL)
printf("signal isn't valid...\n");
else
printf("main thread is active...\n");
printf("son thread end...\n");
pthread_exit(NULL);
}
// main thread
int main(int argc, char **argv)
{
pthread_t son_thread;
int ret;
main_thread = pthread_self(); // get main thread
ret = pthread_create(&son_thread, NULL, thread_func, NULL);
RETURN_ERROR(pthread_create, ret, -1)
ret = pthread_detach(son_thread);
RETURN_ERROR(pthread_detach, ret, -1)
printf("[Main thread]: will end...\n");
pthread_exit(NULL);
return 0;
}
执行结果:
[Main thread]: will end...
main thread has quited or not exist...
son thread end...
可以看出,子线程使用pthread_kill得到主线程的状态为退出或者不存在,其实这也证明了主线程已经提前退出了。
Q6: pthread_kill函数是做什么的?
A: 不要被它的名字吓怕了,它不是专门kill线程的,它只是给线程传信号的。如果信号传递的是0,那么只执行线程是否存在的检查,就像上面的代码那样。但是,如果传入的信号不是0,而是其它信号(或者说是进程信号,如SIGINT, SIGQUIT等),那么线程将需要处理这些信号。如果线程没有注册这些信号的处理函数,那么信号将默认继续传送给进程。也就是说,如果给一个线程传递SIGQUIT信号,但是线程没有注册处理SIGQUIT信号的函数,那么SIGQUIT信号被传递到进程中,进程会结束。
下面看下pthread_kill的源代码:
int
pthread_kill (
pthread_t th,
int sig)
{
int error = 0;
mach_port_t kport = MACH_PORT_NULL;
if ((sig < 0) || (sig > NSIG))
return(EINVAL);
if (_pthread_lookup_thread(th, &kport, 0) != 0)
return (ESRCH); /* Not a valid thread */
/* if the thread is a workqueue thread, just return error */
if ((th->wqthread != 0) && (th->wqkillset ==0)) {
return(ENOTSUP);
}
error = __pthread_kill(kport, sig);
if (error == -1)
error = errno;
return(error);
}
它开始检查了信号是否合法,并查询了线程是否合法,调用了内部函数__pthread_kill发送信号。很可惜,此函数苹果没有公开源代码。
Q7: 对于一个线程A,它创建了子线程B,A结束了,B会结束吗?
A: 可以这么说,一个线程中创建了另一个线程,此时它们没有任何依赖关系的,它们都根据操作系统的调度系统自由运行,该结束就结束,该不结束就不结束。下面有个例子:
void *grandson_thread_func(void *arg)
{
sleep(2);
printf("grandson_thread_func\n");
return NULL;
}
void *thread_func(void *arg)
{
pthread_t grandson_thread;
int ret;
ret = pthread_create(&grandson_thread, NULL, grandson_thread_func, NULL);
RETURN_ERROR(pthread_create, ret, 0)
ret = pthread_detach(grandson_thread);
RETURN_ERROR(pthread_detach, ret, 0)
PRINT_S("son thread will exit")
return NULL;
}
// main thread
int main(int argc, char **argv)
{
pthread_t son_thread;
int ret;
ret = pthread_create(&son_thread, NULL, thread_func, NULL);
RETURN_ERROR(pthread_create, ret, -1)
ret = pthread_detach(son_thread);
RETURN_ERROR(pthread_detach, ret, -1)
while (1) // wait here
;
printf("[Main thread]: will end...\n");
return 0;
}
上面的代码中,主线程创建新线程A,新线程A创建一个新线程B,线程A结束后,如果线程B中的输出依然可以输出,表明A的结束并没有导致B的结束。
运行结果:
"son thread will exit"
grandson_thread_func
可以看到,线程B没有因为创建它的线程A的结束而结束。
Q8: 为什么主线程最后结束后,所有子线程不管运行不运行,都会被中断结束?
A: 这在于主线程执行函数比较特殊,它有_start模块调用,它结束后会调用exit结束进程,这个操作将强制终止进程所有子线程的运行,导致了子线程被中断。而对于子线程来说,就没有这种特殊情况。
Q9: pthread_cancel同样可以取消线程执行,为什么下面代码中的线程没有很快结束?
pthread_key_t key;
int i = 0;
void destructkey_func(void *arg)
{
LOG_ENTER_FUNC
}
void cleanup_func(void *arg)
{
LOG_ENTER_FUNC
PRINT_D(i)
}
void *thread_func(void *arg)
{
int sum;
int ret;
pthread_cleanup_push(cleanup_func, NULL);
ret = pthread_setspecific(key, &i);
RETURN_ERROR(pthread_setspecific, ret, 0)
for(; i < INT_MAX; ++i)
sum += i;
PRINT_S("son thread will exit")
return NULL;
pthread_cleanup_pop(0);
}
// main thread
int main(int argc, char **argv)
{
pthread_t son_thread;
int ret;
ret = pthread_key_create(&key, destructkey_func);
RETURN_ERROR(pthread_key_create, ret, -1)
ret = pthread_create(&son_thread, NULL, thread_func, NULL);
RETURN_ERROR(pthread_create, ret, -1)
// cancel the son thread
ret = pthread_cancel(son_thread);
RETURN_ERROR(pthread_cancel, ret, -1)
// wait son thread to exit
ret = pthread_join(son_thread, NULL);
RETURN_ERROR(pthread_join, ret, -1)
ret = pthread_key_delete(key);
RETURN_ERROR(pthread_key_delete, ret, -1)
printf("[Main thread]: will end...\n");
return 0;
}
主线程创建子线程后,cancel它,但是子线程在for循环中一直无法脱身进入结束状态,这是为什么?
A: 这在于pthread_cancel只是在线程中设置了一个取消的状态,至于线程会不会进入结束状态,这取决于线程是否允许被取消,以及被取消的类型是立即还是延迟的。延迟的就是指需要进入取消点才会进入结束状态, 立即的是指尽快结束线程(但是这也不一定能保证线程一定能满足需求一样尽快结束,内部实现可能依然会有一些等待时间)。上面的代码运行:
[xxxx seconds later]
"son thread will exit"
enter func cleanup_func
i is 2147483647
enter func destructkey_func
[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调用而结束了,如下:
void *thread_func(void *arg)
{
int sum;
int ret;
pthread_cleanup_push(cleanup_func, NULL);
ret = pthread_setspecific(key, &i);
RETURN_ERROR(pthread_setspecific, ret, 0)
for(; i < INT_MAX; ++i)
{
// if cancel mark exists, then will exit the thread as soon as possible
pthread_testcancel();
sum += i;
}
PRINT_S("son thread will exit")
return NULL;
pthread_cleanup_pop(0);
}
上面对子线程代码的修改为for循环中加入了pthread_testcancel函数,它会判断是否取消标志被标示,如果被标示,那么线程将立即准备结束。如下运行结果:
enter func cleanup_func
i is 0
enter func destructkey_func
[Main thread]: will end...
可以看出,for循环最多只跑1遍,线程就结束了;可见pthread_testcancel作用之大!
Q10: 如果采用设置线程取消类型为PTHREAD_CANCEL_ASYNCHRONOUS的方式来尽快可以结束线程,这样线程一定会立即结束吗?
A: 这个不一定。设置这个标志位只保证了会尽快来结束线程,但是调度策略不同,以及线程执行情况不同,都无法保证。如下:
void *thread_func(void *arg)
{
int sum;
int ret;
int old_type;
pthread_cleanup_push(cleanup_func, NULL);
ret = pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &old_type);
RETURN_ERROR(pthread_setcanceltype, ret, 0)
ret = pthread_setspecific(key, &i);
RETURN_ERROR(pthread_setspecific, ret, 0)
for(; i < INT_MAX; ++i)
{
sum += i;
}
PRINT_S("son thread will exit")
return NULL;
pthread_cleanup_pop(0);
}
// main thread
int main(int argc, char **argv)
{
pthread_t son_thread;
int ret;
ret = pthread_key_create(&key, destructkey_func);
RETURN_ERROR(pthread_key_create, ret, -1)
ret = pthread_create(&son_thread, NULL, thread_func, NULL);
RETURN_ERROR(pthread_create, ret, -1)
sleep(1);
// cancel the son thread
ret = pthread_cancel(son_thread);
RETURN_ERROR(pthread_cancel, ret, -1)
PRINT_S("has call cancel the son thread:")
// wait son thread to exit
ret = pthread_join(son_thread, NULL);
RETURN_ERROR(pthread_join, ret, -1)
ret = pthread_key_delete(key);
RETURN_ERROR(pthread_key_delete, ret, -1)
printf("[Main thread]: will end...\n");
return 0;
}
运行结果:
"has call cancel the son thread:"
[xxxxx seconds later]
"son thread will exit"
enter func cleanup_func
i is 2147483647
enter func destructkey_func
[Main thread]: will end...
可以看出,线程还是等待for循环结束才结束。
Q11: 对于pthread_cancel函数,它内部是如何实现的?
A: 如下代码:
int
pthread_cancel(pthread_t thread)
{
#if __DARWIN_UNIX03
if (__unix_conforming == 0)
__unix_conforming = 1;
#endif /* __DARWIN_UNIX03 */
if (_pthread_lookup_thread(thread, NULL, 0) != 0)
return(ESRCH);
/* if the thread is a workqueue thread, then return error */
if (thread->wqthread != 0) {
return(ENOTSUP);
}
#if __DARWIN_UNIX03
int state;
LOCK(thread->lock);
state = thread->cancel_state |= _PTHREAD_CANCEL_PENDING;
UNLOCK(thread->lock);
if (state & PTHREAD_CANCEL_ENABLE)
__pthread_markcancel(thread->kernel_thread); // do cancel mark
#else /* __DARWIN_UNIX03 */
thread->cancel_state |= _PTHREAD_CANCEL_PENDING;
#endif /* __DARWIN_UNIX03 */
return (0);
}
可以很清楚地看到,它内部设置了cancel标志位。对于__pthread_markcancel函数,苹果没有公开代码,只有一个原型:
extern int __pthread_markcancel(int);
另外,我们看下pthread_setcancelstate的源代码:
/*
* Query/update the cancelability 'state' of a thread
*/
int
pthread_setcancelstate(int state, int *oldstate)
{
#if __DARWIN_UNIX03
if (__unix_conforming == 0) {
__unix_conforming = 1;
}
return (_pthread_setcancelstate_internal(state, oldstate, 1));
#else /* __DARWIN_UNIX03 */
return (_pthread_setcancelstate_internal(state, oldstate, 0));
#endif /* __DARWIN_UNIX03 */
}
_pthread_setcancelstate_internal代码如下:
/*
* Query/update the cancelability 'state' of a thread
*/
int
_pthread_setcancelstate_internal(int state, int *oldstate, int conforming)
{
pthread_t self = pthread_self();
switch (state) {
case PTHREAD_CANCEL_ENABLE:
if (conforming)
__pthread_canceled(1);
break;
case PTHREAD_CANCEL_DISABLE:
if (conforming)
__pthread_canceled(2);
break;
default:
return EINVAL;
}
self = pthread_self();
LOCK(self->lock);
if (oldstate)
*oldstate = self->cancel_state & _PTHREAD_CANCEL_STATE_MASK;
self->cancel_state &= ~_PTHREAD_CANCEL_STATE_MASK;
self->cancel_state |= state;
UNLOCK(self->lock);
if (!conforming)
_pthread_testcancel(self, 0); /* See if we need to 'die' now... */
return (0);
}
它的主要作用是设置cancel_state标志位; 同时,它对于是否允许取消线程也做了不同的处理。
对于pthread_setcanceltype函数,代码如下:
/*
* Query/update the cancelability 'type' of a thread
*/
int
pthread_setcanceltype(int type, int *oldtype)
{
pthread_t self = pthread_self();
#if __DARWIN_UNIX03
if (__unix_conforming == 0)
__unix_conforming = 1;
#endif /* __DARWIN_UNIX03 */
if ((type != PTHREAD_CANCEL_DEFERRED) &&
(type != PTHREAD_CANCEL_ASYNCHRONOUS))
return EINVAL;
self = pthread_self();
LOCK(self->lock);
if (oldtype)
*oldtype = self->cancel_state & _PTHREAD_CANCEL_TYPE_MASK;
self->cancel_state &= ~_PTHREAD_CANCEL_TYPE_MASK;
self->cancel_state |= type;
UNLOCK(self->lock);
#if !__DARWIN_UNIX03
_pthread_testcancel(self, 0); /* See if we need to 'die' now... */
#endif /* __DARWIN_UNIX03 */
return (0);
}
它主要做了设置cancel_state标志位的事情。
pthread_testcancel函数做了如下:
void
pthread_testcancel(void)
{
pthread_t self = pthread_self();
#if __DARWIN_UNIX03
if (__unix_conforming == 0)
__unix_conforming = 1;
_pthread_testcancel(self, 1);
#else /* __DARWIN_UNIX03 */
_pthread_testcancel(self, 0);
#endif /* __DARWIN_UNIX03 */
}
内部调用的函数_pthread_testcancel代码如下:
/*
* Insert a cancellation point in a thread.
*/
__private_extern__ void
_pthread_testcancel(pthread_t thread, int isconforming)
{
LOCK(thread->lock);
if ((thread->cancel_state & (PTHREAD_CANCEL_ENABLE|_PTHREAD_CANCEL_PENDING)) ==
(PTHREAD_CANCEL_ENABLE|_PTHREAD_CANCEL_PENDING))
{
UNLOCK(thread->lock);
if (isconforming)
pthread_exit(PTHREAD_CANCELED);
else
pthread_exit(0);
}
UNLOCK(thread->lock);
}
可以看出,它在必要的时刻,调用了pthread_exit来结束线程。
最后,顺便贴下pthread_create的源代码(因为调用函数太多,只贴出前2个级别调用):
int
pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg)
{
return _new_pthread_create_suspended(thread, attr, start_routine, arg, 0);
}
内部函数调用:
static int
_new_pthread_create_suspended(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg,
int create_susp)
{
pthread_attr_t *attrs;
void *stack;
int error;
unsigned int flags;
pthread_t t,t2;
kern_return_t kern_res;
mach_port_t kernel_thread = MACH_PORT_NULL;
int needresume;
task_t self = mach_task_self();
int kernalloc = 0;
int susp = create_susp;
if ((attrs = (pthread_attr_t *)attr) == (pthread_attr_t *)NULL)
{ /* Set up default paramters */
attrs = &_pthread_attr_default;
} else if (attrs->sig != _PTHREAD_ATTR_SIG) {
return EINVAL;
}
error = 0;
if (((attrs->policy != _PTHREAD_DEFAULT_POLICY) ||
(attrs->param.sched_priority != default_priority)) && (create_susp == 0)) {
needresume = 1;
susp = 1;
} else
needresume = 0;
/* In default policy (ie SCHED_OTHER) only sched_priority is used. Check for
* any change in priority or policy is needed here.
*/
if ((__oldstyle == 1) || (create_susp != 0)) {
/* Rosetta or pthread_create_suspended() */
/* running under rosetta */
/* Allocate a stack for the thread */
#if PTH_TRACE
__kdebug_trace(0x9000000, create_susp, 0, 0, 0, 0);
#endif
if ((error = _pthread_allocate_stack(attrs, &stack)) != 0) {
return(error);
}
t = (pthread_t)malloc(sizeof(struct _pthread));
*thread = t;
if (susp) {
/* Create the Mach thread for this thread */
PTHREAD_MACH_CALL(thread_create(self, &kernel_thread), kern_res);
if (kern_res != KERN_SUCCESS)
{
printf("Can't create thread: %d\n", kern_res);
return(EINVAL);
}
}
if ((error = _pthread_create(t, attrs, stack, kernel_thread)) != 0)
{
return(error);
}
set_malloc_singlethreaded(0);
__is_threaded = 1;
/* Send it on it's way */
t->arg = arg;
t->fun = start_routine;
t->newstyle = 0;
/* Now set it up to execute */
LOCK(_pthread_list_lock);
TAILQ_INSERT_TAIL(&__pthread_head, t, plist);
#if PTH_LISTTRACE
__kdebug_trace(0x900000c, t, 0, 0, 4, 0);
#endif
_pthread_count++;
UNLOCK(_pthread_list_lock);
_pthread_setup(t, _pthread_body, stack, susp, needresume);
return(0);
} else {
flags = 0;
if (attrs->fastpath == 1)
kernalloc = 1;
if (attrs->detached == PTHREAD_CREATE_DETACHED)
flags |= PTHREAD_START_DETACHED;
if (attrs->schedset != 0) {
flags |= PTHREAD_START_SETSCHED;
flags |= ((attrs->policy & PTHREAD_START_POLICY_MASK) << PTHREAD_START_POLICY_BITSHIFT);
flags |= (attrs->param.sched_priority & PTHREAD_START_IMPORTANCE_MASK);
}
set_malloc_singlethreaded(0);
__is_threaded = 1;
if (kernalloc == 0) {
/* Allocate a stack for the thread */
flags |= PTHREAD_START_CUSTOM;
if ((error = _pthread_create_pthread_onstack(attrs, &stack, &t)) != 0) {
return(error);
}
/* Send it on it's way */
t->arg = arg;
t->fun = start_routine;
t->newstyle = 1;
#if PTH_TRACE
__kdebug_trace(0x9000004, t, flags, 0, 0, 0);
#endif
if ((t2 = __bsdthread_create(start_routine, arg, stack, t, flags)) == (pthread_t)-1) {
_pthread_free_pthread_onstack(t, 1, 0);
return (EAGAIN);
}
else t=t2;
LOCK(_pthread_list_lock);
t->parentcheck = 1;
if ((t->childexit != 0) && ((t->detached & PTHREAD_CREATE_DETACHED) == PTHREAD_CREATE_DETACHED)) {
/* detached child exited, mop up */
UNLOCK(_pthread_list_lock);
#if PTH_TRACE
__kdebug_trace(0x9000008, t, 0, 0, 1, 0);
#endif
if(t->freeStackOnExit)
vm_deallocate(self, (mach_vm_address_t)(uintptr_t)t, pthreadsize);
else
free(t);
} else if (t->childrun == 0) {
TAILQ_INSERT_TAIL(&__pthread_head, t, plist);
_pthread_count++;
#if PTH_LISTTRACE
__kdebug_trace(0x900000c, t, 0, 0, 1, 0);
#endif
UNLOCK(_pthread_list_lock);
} else
UNLOCK(_pthread_list_lock);
*thread = t;
#if PTH_TRACE
__kdebug_trace(0x9000014, t, 0, 0, 1, 0);
#endif
return (0);
} else {
/* kernel allocation */
#if PTH_TRACE
__kdebug_trace(0x9000018, flags, 0, 0, 0, 0);
#endif
if ((t = __bsdthread_create(start_routine, arg, (void *)attrs->stacksize, NULL, flags)) == (pthread_t)-1)
return (EAGAIN);
/* Now set it up to execute */
LOCK(_pthread_list_lock);
t->parentcheck = 1;
if ((t->childexit != 0) && ((t->detached & PTHREAD_CREATE_DETACHED) == PTHREAD_CREATE_DETACHED)) {
/* detached child exited, mop up */
UNLOCK(_pthread_list_lock);
#if PTH_TRACE
__kdebug_trace(0x9000008, t, pthreadsize, 0, 2, 0);
#endif
vm_deallocate(self, (mach_vm_address_t)(uintptr_t)t, pthreadsize);
} else if (t->childrun == 0) {
TAILQ_INSERT_TAIL(&__pthread_head, t, plist);
_pthread_count++;
#if PTH_LISTTRACE
__kdebug_trace(0x900000c, t, 0, 0, 2, 0);
#endif
UNLOCK(_pthread_list_lock);
} else
UNLOCK(_pthread_list_lock);
*thread = t;
#if PTH_TRACE
__kdebug_trace(0x9000014, t, 0, 0, 2, 0);
#endif
return(0);
}
}
}
另外,贴下pthread_t结构:
typedef struct _pthread
{
long sig; /* Unique signature for this structure */
struct __darwin_pthread_handler_rec *__cleanup_stack;
pthread_lock_t lock; /* Used for internal mutex on structure */
uint32_t detached:8,
inherit:8,
policy:8,
freeStackOnExit:1,
newstyle:1,
kernalloc:1,
schedset:1,
wqthread:1,
wqkillset:1,
pad:2;
size_t guardsize; /* size in bytes to guard stack overflow */
#if !defined(__LP64__)
int pad0; /* for backwards compatibility */
#endif
struct sched_param param;
uint32_t cancel_error;
#if defined(__LP64__)
uint32_t cancel_pad; /* pad value for alignment */
#endif
struct _pthread *joiner;
#if !defined(__LP64__)
int pad1; /* for backwards compatibility */
#endif
void *exit_value;
semaphore_t death; /* pthread_join() uses this to wait for death's call */
mach_port_t kernel_thread; /* kernel thread this thread is bound to */
void *(*fun)(void*);/* Thread start routine */
void *arg; /* Argment for thread start routine */
int cancel_state; /* Whether thread can be cancelled */
int err_no; /* thread-local errno */
void *tsd[_EXTERNAL_POSIX_THREAD_KEYS_MAX + _INTERNAL_POSIX_THREAD_KEYS_MAX]; /* Thread specific data */
void *stackaddr; /* Base of the stack (is aligned on vm_page_size boundary */
size_t stacksize; /* Size of the stack (is a multiple of vm_page_size and >= PTHREAD_STACK_MIN) */
mach_port_t reply_port; /* Cached MiG reply port */
#if defined(__LP64__)
int pad2; /* for natural alignment */
#endif
void *cthread_self; /* cthread_self() if somebody calls cthread_set_self() */
/* protected by list lock */
uint32_t childrun:1,
parentcheck:1,
childexit:1,
pad3:29;
#if defined(__LP64__)
int pad4; /* for natural alignment */
#endif
TAILQ_ENTRY(_pthread) plist;
void * freeaddr;
size_t freesize;
mach_port_t joiner_notify;
char pthread_name[MAXTHREADNAMESIZE]; /* including nulll the name */
int max_tsd_key;
void * cur_workq;
void * cur_workitem;
uint64_t thread_id;
} *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]
转载请注明出处
每日总结: 优秀的架构都是类似的,垃圾的架构一般都是一个原因:代码内部原理掌握得不够