[Python-Dev] A few lessons from the tempfile.py rewrite

Tim Peters tim.one@comcast.net
Sat, 17 Aug 2002 11:12:13 -0400


[Zack Weinberg]
> ...
> 2) pthread_once equivalent.
>
> pthread_once is a handy function in the C pthreads library which
> can be used to guarantee that some data object is initialized exactly
> once, and no thread sees it in a partially initialized state.

I don't know that it comes up enough in Python to bother doing something
about it -- as Guido said, there's an import lock under the covers that
ensures only one thread executes module init code (== all "top level" code
in a module).  So modules that need one-shot initialization can simply do it
at module level.  tempfile has traditionally gone overboard in avoiding use
of this feature, though.

A more Pythonic approach may be gotten via emulating pthread_once more
closely, forgetting the "data object" business in favor of executing
arbitrary functions "just once".  Like so, maybe:

def do_once(func, lock=threading.RLock(), done={}):
    if func not in done:
        lock.acquire()
        try:
            if func not in done:
                func()
                done[func] = True
        finally:
            lock.release()

"done" is a set of function objects that have already been run, represented
by a dict mapping function objects to bools (although the dict values make
no difference, only key presence matters).  Default arguments are abused
here to give do_once persistent bindings to objects without polluting the
global namespace.  A more purist alternative is

def do_once(func):
    if func not in do_once.done:
        do_once.lock.acquire()
        try:
            if func not in do_once.done:
                func()
                do_once.done[func] = True
        finally:
            do_once.lock.release()

do_once.lock = threading.RLock()
do_once.done = {}

This is "more Pythonic", chiefly in not trying to play presumptive games
with namespaces.  If some module M wants to set its own attr goob, fine, M
can do

def setgoob():
    global goob
    goob = 42

do_once(setgoob)

and regardless of which module do_once came from.  Now what setgoob does is
utterly obvious, and do_once() doesn't make helpful assumptions that get in
the way <wink>.