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

作者:陈曦

日期: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] 

转载请注明出处

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



  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值