gpt4 book ai didi

python - 设置当线程以其他方式完成时要执行的行为

转载 作者:行者123 更新时间:2023-12-04 19:25:59 25 4
gpt4 key购买 nike

我的模块中有两个功能:do_something() , 和 change_behavior() .

函数do_something()默认情况下执行 A。后 change_behavior()已被调用,do_something()而是做 B 事情。

我希望这种转换是特定于线程的。也就是说,任何新线程在调用 do_something() 时都会发生事件 A。 ,但如果该线程调用 change_behavior() ,那么当 B 继续调用 do_something() 时,它会发生。 .

每个线程应该是独立的,这样一个线程调用 change_behavior()不影响 do_something() 的行为对于其他线程。

我对此的本能解决方案是将行为与线程的 ID 相关联(通过 threading.get_ident() 评估)。函数do_something()检查本地表中是否存在线程的 ID,并相应地调整其行为。同时,函数change_behavior()只需将当前线程添加到该注册表中即可。这在任何给定时间都有效,因为永远不会有两个具有相同 ID 的并发线程。

当当前线程集加入时,问题就出现了,随着时间的流逝,父线程创建了更多线程。新线程之一与先前线程之一具有相同的 ID,因为有时会重复使用线程 ID。该线程调用 do_something() ,并且因为它已经在注册表中,所以它执行的是 B 而不是 A。

为了解决这个问题,我需要以某种方式从注册表中删除线程 ID,在具有该 ID 的第一个线程结束和具有该 ID 的第二个线程开始之间。我提出的一些假设性想法:

  • 定期检查每个线程 ID 是否仍处于事件状态。这是有问题的,因为它既浪费 CPU 资源,又会在线程被破坏然后在滴答之间重新创建时丢失
  • 附加一个在线程加入时要调用的方法钩子(Hook)。除了下一个想法,我不知道该怎么做
  • 作为 change_behavior() 的一部分, 劫持/替换当前线程的 ._quit()方法首先从注册表中删除线程的 ID。这似乎是不好的做法,并且可能会破坏。

  • 我的用例的另一个方面是,如果可能的话,我希望新线程继承其父线程的当前行为,以便用户不必手动设置他们创建的每个标志 - 但这与与线程完成时相比,我如何存储有关胎面状态的信息,这使得它与这个特定问题的相关性略有下降。

    我正在寻找有关这些特定解决方案中的任何一个是理想的、标准的还是惯用的,以及在此用例中是否有计划要做的事情的指导。

    使用 threading.local() @TarunLalwani 在评论中提出了建议。我已经调查过了,它很有用,但它没有考虑到我想要处理的其他用例 - 当父线程创建新的子线程时,我希望它们继承父线程的状态.我想通过替换 Thread.__init__() 来实现这一点。 ,但使用 local()通常与此用例不兼容,因为我无法将变量从父线程传递到子线程。

    我也一直在试验,更成功的是,只需将我的属性保存到线程本身:
    current_thread = threading.current_thread()
    setattr(current_thread, my_reference, new_value)

    这样做的问题是,由于一个完全让我感到困惑的原因,模块命名空间中的任何其他变量的值当前为 current_thread.my_reference也设置为 new_value .我不知道为什么,而且我一直无法在 MVE 中复制这个问题(尽管它在我的 IDE 中一直发生,即使在重新启动它之后)。如 my other currently-active question暗示,我在这里设置的对象是对输出流的引用(我在该答案中描述的对中间 IO 流实例的每个引用都被调用此方法的文件描述符替换),如果有的话与它有关,但我无法想象为什么对象的类型会影响引用在这种情况下的工作方式。

    最佳答案

    我的回答是对你问题的一个非常简单的回答,因此我想知道我是否遗漏了什么。基本上,我认为您应该避免在模块中存储外部对象的当前状态。

    您需要在某处存储状态(如果 change_behavior 被调用,也许还有一些其他数据)。您有两个主要选择:将状态存储在模块中或将状态存储在线程本身中。除了您在模块中存储状态时遇到的问题之外,人们希望模块(主要)是无状态的,因此我认为您应该坚持后者并将数据存储在线程中。

    版本 1

    如果您将状态存储在字段中,您创建的属性名称和现有属性名称之间会有一点冲突的风险,但是如果文档很清楚并且您选择了一个好的名称,那么这不应该是一个问题。

    一个简单的概念证明,没有 setattrhasattr (我没有检查 CPython 的源代码,但也许奇怪的行为来自 setattr ):

    模块1.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

    测试1.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的所有者子类和您避免名称冲突的风险。另外,在我看来,代码变得更干净了:

    模块2.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()

    测试2.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)>)

    缺点是即使您不需要线程,也必须使用它。

    关于python - 设置当线程以其他方式完成时要执行的行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56959718/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com