int pthread_once(pthread_once_t *once_control, void (*init_routine) (void));
pthread_once函数在用于仅仅调用一次初始化 它的代码比想象中的复杂一些 本来直接用一个原子的test and set bit操作就可以防止执行多次,但是pthread_once 的一个重要功能就是让其它线程睡眠等待而不是直接不等待init_routine执行完就继续 还要考虑正在执行的时候被fork或者被cancel的问题
.globl __pthread_once
.type __pthread_once,@function
.align 16
__pthread_once:
movl 4(%esp), %ecx
只有执行过init_routine之后bit 1才会被设置
testl $2,(%ecx)
jz 1f
xorl %eax, %eax
ret
1: pushl %ebx
pushl %esi
movl %ecx, %ebx
xorl %esi, %esi
/*Not yet initialized or initialization in progress.
Get the fork generation counter now.*/
6: movl (%ebx), %eax
5: movl %eax, %edx
testl $2, %eax
jnz 4f
__fork_generation在fork的时候会被 +4
andl $3, %edx
orl __fork_generation, %edx
orl $1, %edx
原子地设置并交换bit 0,该位表示有线程正在执行
LOCK
cmpxchgl %edx,(%ebx)
jnz 5b
/* Check whether another thread already runs the initializer.*/
testl $1, %eax
jz 3f /* No -> do it.*/
fork的问题, 如果正在执行init_routine的时候fork, 其子进程
调用pthread_once就会到这,这样会执行init_routine,下面那个例子可以看出来
xorl %edx, %eax
testl $0xfffffffc, %eax
jnz 3f /* Different for generation -> run initializer.*/
/* Somebody else got here first.Wait.*/
等待别的线程执行init_routine然后唤醒
movl $FUTEX_WAIT|FUTEX_PRIVATE_FLAG, %ecx
movl $SYS_futex, %eax
ENTER_KERNEL
之所以要重新检查,是因为init_routine在执行的时候 它可能被杀死
jmp 6b
3:
subl $UNWINDBUFSIZE+8, %esp
movl %ecx, %ebx /* PIC register value.*/
set
leal 8+UWJMPBUF(%esp), %eax
movl $0, 4(%esp)
movl %eax,(%esp)
call __sigsetjmp@PLT
testl %eax, %eax
jne 7f
setjmp == 0,注册cancel jmpbuf
这样,线程在退出的时候会到这里来,上面那条
jne 7f会处理这种情况
leal 8(%esp), %eax
call HIDDEN_JUMPTARGET(__pthread_register_cancel)
/*Call the user-provided initialization function.*/