我的回答是对您问题的一个非常简单的回答,因此我想知道我是否遗漏了什么。基本上,我认为您应该避免在模块中存储外部对象的当前状态。
您需要在某处存储状态(如果调用了change_behavior 并且可能还有其他一些数据)。您有两个主要选择:将状态存储在模块中或将状态存储在线程本身中。除了在模块中存储状态时遇到的问题,人们希望模块(主要)是无状态的,因此我认为您应该坚持后者并将数据存储在线程中。
版本 1
如果您将状态存储在字段中,您创建的属性名称和现有属性的名称之间会有一点冲突的风险,但是如果文档清晰并且您选择了一个好的名称,那应该不是问题。
一个简单的概念证明,没有setattr或hasattr(我没有检查CPython的源代码,但可能奇怪的行为来自setattr):
module1.py
>
import threading
import random
import time
_lock = threading.Lock()
def do_something():
with _lock:
t = threading.current_thread()
try:
if t._my_module_s:
print(f"DoB ({t})")
else:
print(f"DoA ({t})")
except AttributeError:
t._my_module_s = 0
print(f"DoA ({t})")
time.sleep(random.random()*2)
def change_behavior():
with _lock:
t = threading.current_thread()
print(f"Change behavior of: {t}")
t._my_module_s = 1
test1.py
>
import random
import threading
from module1 import *
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
n = random.randint(1, 10)
for i in range(n):
do_something()
change_behavior()
for i in range(10-n):
do_something()
thread_1 = MyThread()
thread_2 = MyThread()
thread_1.start()
thread_2.start()
thread_1.join()
thread_2.join()
输出 1
DoA (<MyThread(Thread-1, started 140155115792128)>)
DoA (<MyThread(Thread-2, started 140155107399424)>)
DoA (<MyThread(Thread-1, started 140155115792128)>)
DoA (<MyThread(Thread-1, started 140155115792128)>)
Change behavior of: <MyThread(Thread-1, started 140155115792128)>
DoB (<MyThread(Thread-1, started 140155115792128)>)
DoB (<MyThread(Thread-1, started 140155115792128)>)
DoA (<MyThread(Thread-2, started 140155107399424)>)
DoB (<MyThread(Thread-1, started 140155115792128)>)
DoA (<MyThread(Thread-2, started 140155107399424)>)
DoB (<MyThread(Thread-1, started 140155115792128)>)
DoA (<MyThread(Thread-2, started 140155107399424)>)
DoA (<MyThread(Thread-2, started 140155107399424)>)
DoB (<MyThread(Thread-1, started 140155115792128)>)
DoA (<MyThread(Thread-2, started 140155107399424)>)
Change behavior of: <MyThread(Thread-2, started 140155107399424)>
DoB (<MyThread(Thread-2, started 140155107399424)>)
DoB (<MyThread(Thread-1, started 140155115792128)>)
DoB (<MyThread(Thread-1, started 140155115792128)>)
DoB (<MyThread(Thread-2, started 140155107399424)>)
DoB (<MyThread(Thread-2, started 140155107399424)>)
DoB (<MyThread(Thread-2, started 140155107399424)>)
版本 2
如果您确定最终用户会在线程中使用您的模块,您可以为他/她提供一种方便的方式来执行此操作。这个想法是自己处理线程。只需将用户函数包装在一个线程中,并将线程的状态存储在该线程中,如上。不同之处在于您是Thread 子类的所有者,您可以避免名称冲突的风险。另外,在我看来,代码变得更干净了:
module2.py
>
import threading
import random
import time
_lock = threading.Lock()
def do_something():
with _lock:
t = threading.current_thread()
t.do_something() # t must be a _UserFunctionWrapper
time.sleep(random.random()*2)
def change_behavior():
with _lock:
t = threading.current_thread()
t.change_behavior() # t must be a _UserFunctionWrapper
def wrap_in_thread(f):
return _UserFunctionWrapper(f)
class _UserFunctionWrapper(threading.Thread):
def __init__(self, user_function):
threading.Thread.__init__(self)
self._user_function = user_function
self._s = 0
def change_behavior(self):
print(f"Change behavior of: {self}")
self._s = 1
def do_something(self):
if self._s:
print(f"DoB ({self})")
else:
print(f"DoA ({self})")
def run(self):
self._user_function()
test2.py
>
import random
from module2 import *
def user_function():
n = random.randint(1, 10)
for i in range(n):
do_something() # won't work if the function is not wrapped
change_behavior()
for i in range(10-n):
do_something()
thread_1 = wrap_in_thread(user_function)
thread_2 = wrap_in_thread(user_function)
thread_1.start()
thread_2.start()
thread_1.join()
thread_2.join()
输出 2
DoA (<_UserFunctionWrapper(Thread-1, started 140193896072960)>)
DoA (<_UserFunctionWrapper(Thread-2, started 140193887680256)>)
DoA (<_UserFunctionWrapper(Thread-2, started 140193887680256)>)
Change behavior of: <_UserFunctionWrapper(Thread-1, started 140193896072960)>
DoB (<_UserFunctionWrapper(Thread-1, started 140193896072960)>)
DoB (<_UserFunctionWrapper(Thread-1, started 140193896072960)>)
DoA (<_UserFunctionWrapper(Thread-2, started 140193887680256)>)
DoA (<_UserFunctionWrapper(Thread-2, started 140193887680256)>)
DoB (<_UserFunctionWrapper(Thread-1, started 140193896072960)>)
DoB (<_UserFunctionWrapper(Thread-1, started 140193896072960)>)
DoA (<_UserFunctionWrapper(Thread-2, started 140193887680256)>)
DoB (<_UserFunctionWrapper(Thread-1, started 140193896072960)>)
DoA (<_UserFunctionWrapper(Thread-2, started 140193887680256)>)
DoB (<_UserFunctionWrapper(Thread-1, started 140193896072960)>)
DoA (<_UserFunctionWrapper(Thread-2, started 140193887680256)>)
DoB (<_UserFunctionWrapper(Thread-1, started 140193896072960)>)
DoA (<_UserFunctionWrapper(Thread-2, started 140193887680256)>)
DoA (<_UserFunctionWrapper(Thread-2, started 140193887680256)>)
Change behavior of: <_UserFunctionWrapper(Thread-2, started 140193887680256)>
DoB (<_UserFunctionWrapper(Thread-2, started 140193887680256)>)
DoB (<_UserFunctionWrapper(Thread-1, started 140193896072960)>)
DoB (<_UserFunctionWrapper(Thread-1, started 140193896072960)>)
缺点是即使不需要也必须使用线程。