誰がthreadingで作ったスレッドの後始末をしているのか

メインスレッドが終了すると、_threadモジュールで作ったスレッドは終了してしまう。

#! python3.3

import _thread
import time

def func():
    ident = _thread.get_ident()
    print('thread {} starts'.format(ident))
    time.sleep(5)
    print('thread {} ends'.format(ident))

print('main thread starts')
_thread.start_new_thread(func, ())
time.sleep(3)
print('main thread ends')

""" 出力:
main thread starts
thread 1756 starts
main thread ends
"""

一方、threadingモジュールで作ったスレッドは終わるまで自動で待ってくれる。

#! python3.3

import threading
import time

def func():
    ident = threading.get_ident()
    print('thread {} starts'.format(ident))
    time.sleep(5)
    print('thread {} ends'.format(ident))

print('main thread starts')
threading.Thread(target=func).start()
time.sleep(3)
print('main thread ends')

""" 出力:
main thread starts
thread 3612 starts
main thread ends
thread 3612 ends
"""

この待つ処理(join)は誰がやっているんだろうと思って、threading.pyの中身を見てみると、_MainThreadクラスの_exitfuncメソッドが実際にjoin()してた。しかし、この_exitfuncというメソッドは_shutdownという別名がついているだけで、誰が呼んでいるのか不明なんだ。__del__もないし、atexitも呼んでいない。どうやってPython終了時に実行してるんだろう?

そこで検索してみたら http://sh1.2-d.jp/b/2008-01-10-02-17.html に書いてあった。現在のPythonでは少し仕組みが変わっているが、呼び出し順はこう。

Modules/main.cのPy_Main
    ↓
Python/pythonrun.cのPy_Finalize
    ↓
同ファイルのwait_for_thread_shutdown
    ↓
threading.pyの_shutdown

しかしなあ。これ実際の中身は「もしthreadingという名前のモジュールが読み込まれていたら_shutdownを実行する」という形だからな。「もしthreadingという同名のモジュールがあったら?*1」とか「高水準なモジュールの後始末をインタプリタが名指しでするの?」とか色々な疑問が頭をよぎってしまう。まぁでも、threadingのドキュメントを見る限り、Pythonのスレッドは発展途中っぽいからな。GILの件もあるし。単に一時しのぎ的な実装になっているだけなのかもしれない。

*1:実際にやってみると呼ばれる。