目录


概述[返回到目录]

有人可能没听过fork safety,但是这种情况确实也挺常见。比如,当我们使用uWSGI做WSGI Server的时候,如果采用prefork模型,那么默认情况下,uWSGI的master进程会先加载WSGI Application,然后fork出若干个子进程。此时,如果我们把PyMongo的MongoClient放在全局,那么就会导致uWSGI在fork之前,建立MongoClient实例,因为PyMongo是fork unsafety的,所以这种情况会出问题,更多详情请移步:pymongo: MongoClient opened before fork错误排解
thread safety应该是耳熟能详的,本文主要参照了维基百科的Thread_safety进行讲解。在这里,也举一个线程不安全的例子:Python的Gearman客户端是非线程安全的,当多个线程并发地提交任务、读取响应的时候,就会出现协议解析相关的错误。


fork safety简介[返回到目录]

下面再看一个例子:

    
[root@iZj6chejzrsqpclb7miryaZ ~]# cat test.py 
# coding: utf8

import os
from threading import Lock
import time
import sys

# 生成一个Lock对象,并在fork之前获取它
lock = Lock()
lock.acquire()

# fork出一个子进程
pid = os.fork()

if pid == 0:
    # 子进程

    print u"子进程:pid是%s" % os.getpid()
    print u"子进程:尝试获取锁"
    lock.acquire()
    print u"子进程:成功获取锁"
    print u"子进程:退出"
elif pid > 0:
    # 父进程
    print u"父进程:子进程pid是%d" % pid
    time.sleep(2)
    print u"父进程:释放锁"
    lock.release()
    time.sleep(3)
else:
    sys.exit("fork() error")

[root@iZj6chejzrsqpclb7miryaZ ~]# python test.py 
父进程:子进程pid是18248
子进程:pid是18248
子进程:尝试获取锁
父进程:释放锁
[root@iZj6chejzrsqpclb7miryaZ ~]# strace -p 18248
strace: Process 18248 attached
futex(0x1c19ec0, FUTEX_WAIT_PRIVATE, 0, NULL^Cstrace: Process 18248 detached
 <detached ...>
    

通过strace,可以看到子进程死锁了。
为了解开这个疑惑,我们应该先看一下:fork()函数的使用说明,其中有一段话:

The child process is created with a single thread—the one that called fork(). The entire virtual address space of the parent is replicated in the child, including the states of mutexes, condition variables, and other pthreads objects; the use of pthread_atfork(3) may be helpful for dealing with problems that this can cause.

翻译过来就是,fork出来的子进程中,只有一个线程,就是调用fork()函数的那个线程。父进程的整个虚拟地址空间都会被复制到子进程中,包含互斥变量、条件变量和其它线程对象的状态。为了解决由此导致的问题,可以使用pthread_at_fork()函数。
回过头来看代码:在fork子进程的时候,主进程的主线程持有锁。因此,在fork之后,子进程继承了一把已经被获取的锁,然后在子进程中acquire这把锁,就会产生死锁,进而导致子进程HANG住。
需要额外说明的是:在一个进程退出之后,操作系统会扫描它有哪些子进程,这样的子进程被称为“孤儿进程”,操作系统会将“孤儿进程”过继给pid为1的那个进程(比如,在centos6中是init进程,在centos7中是systemd进程)。
这个例子说明了,如果父进程中持有一把锁,然后执行fork()操作,是不安全的。


thread safety简介[返回到目录]

1,什么是线程安全
线程安全代码可以确保多个线程并发地操作共享数据结构时,它们的行为是正确的。实现线程安全的数据结构有多种策略,后面会逐一地进行介绍。
线程安全是这样一种特性:它允许运行在多线程环境下的代码,通过同步的方式,重新建立程序的真正的执行流和程序文本之间的一致性
2,如何实现线程安全
为了避免竞态条件说明1,达到线程安全的目的,通常有两类方式:


参考资料[返回到目录]


说明[返回到目录]