了解Python中的多线程和锁(概念和示例)

发布于 2025-02-01 11:48:50 字数 2735 浏览 3 评论 0原文

我对使用它进行编程项目的多线程进行了研究(这里是第一名...)。如果您认为我的陈述在下面正确,或者对错误或需要更正的评论表示感谢,我将不胜感激。

  1. 锁定是一个可以通过引用传递给函数,方法的对象。然后,(在此示例中)函数可以使用该锁定对象引用,以便在数据上安全操作(此示例中的一个变量)。它通过获取锁,修改变量然后释放锁来实现此目的。
  2. 可以创建线程以定位一个函数,该功能可以获取对锁定的引用(然后实现上面所述的内容)。
  3. 锁确实不是保护特定变量,对象等。
  4. 除非获得(并释放),否则锁不会保护或做任何东西。
  5. 因此,为了实现所需的保护,锁定 使用 的责任。
  6. 如果在线程A执行的函数中获取锁目的。
  7. 仅当线程B的函数B想要获取相同锁定(即通过相同的引用锁定对象),该功能已经由螺纹A当时针对的功能获取,锁定锁定的锁在该线程中的两个线程上,b将停止进一步执行,直到螺纹A针对的功能再次释放锁。
  8. 因此,如果其目标函数想要(并等待)获取相同的锁定本身,则仅锁定锁才会停止执行线程。因此,通过螺纹获取锁定,它只能 防止线程B获得相同的锁,仅此而已。

如果我想在设置变量时使用锁来防止

  1. 比赛
  2. 条件在设置变量之前,每个函数和每个函数中的锁定(并在此之后发布)。 (*)
  3. 如果我只创建一个针对函数的线程而不提供对锁定对象的引用并设置变量的线程,或者
  4. 如果我通过其目标函数具有锁定的线程设置变量对象,但在操作之前没有获取它,我将失败以实现变量的线程安全设置。

(*)只要其他线程不能访问变量,就应获取锁定。现在,我想将其与数据库事务进行比较...我锁定数据库(〜获取锁定),直到我的指令集完成为止,然后我提交(〜释放锁定)。


示例如果我想创建一个成员_Value的类:

    class Version1:
        def __init__(self):
            self._value:int = 0
            self._lock:threading.Lock = threading.Lock()
        
        def getValue(self) -> int:
            """Getting won't be protected in this example."""
            return self._value
        
        def setValue(self, val:int) -> None:
            """This will be made thread-safe by member lock."""
            with self._lock:
                self._value = val
            
    v1 = Version1()
    t1_1 = threading.Thread(target=v1.setValue, args=(1)).start()
    t1_2 = threading.Thread(target=v1.setValue, args=(2)).start()
    
    
    class Version2:
        def __init__(self):
            self._value:int = 0
        
        def getValue(self) -> int:
            """Getting won't be protected in this example."""
            return self._value
        
        def setValue(self, val:int, lock:threading.Lock) -> None:
            """This will be made thread-safe by injected lock."""
            with self._lock:
                self._value = val
            
    v2 = Version2()
    l = threading.Lock()
    t2_1 = threading.Thread(target=v2.setValue, args=(1, l)).start()
    t2_2 = threading.Thread(target=v2.setValue, args=(2, l)).start()
  1. 版本1中,i作为类提供者,可以保证设置_value始终是线程安全...
  2. ...因为在process2 ,我的班级用户可能会将不同的锁定对象传递给两个产卵线程,从而使锁定保护毫无用处。
  3. 如果我想为我的班级用户提供将_Value设置设置的自由,将应该以线程安全的方式执行的更大步骤集合,我可以注入lock引用版本1's __ INT __功能,并将其分配给_lock成员。因此,可以保证课程的线程安全操作,同时仍允许类的用户为此目的使用“她自己的”锁定。

现在,0-15的分数将评分我(MIS)理解锁的程度...:D

I did research on multi-threading for a programming project using it (first-timer here...). I would appreciate if you deemed my statements below correct or, rather, comment on the ones that are wrong or need correction.

  1. A lock is an object which can be passed to functions, methods, ... by reference. A (in this example) function can then make use of that lock object reference in order to safely operate on data (a variable in this example). It does this by acquiring the lock, modifying the variable and then releasing the lock.
  2. A thread can be created to target a function, which may obtain a reference to a lock (to then achieve what is stated above).
  3. A lock does not protect a specific variable, object etc.
  4. A lock does not protect or do anything unless it is acquired (and released).
  5. Thus, it is in the responsibility of the programmer to use the lock in order achieve the desired protection.
  6. If a lock is acquired inside a function executed by thread A, this has no immediate influence on any other running thread B. Not even if the functions targeted by threads A and B have a reference to the same lock object.
  7. Only if the function targeted by thread B wants to acquire the same lock (i.e. via the same referenced lock object), which already was acquired by the function targeted by thread A at that time, the lock conveys influence on both threads in that thread B will pause further execution until the function targeted by thread A releases the lock again.
  8. Thus, a locked lock only ever pauses execution of a thread, if its targeted function wants (and waits) to acquire the very same lock itself. Thus, by thread A acquiring the lock, it can only prevent thread B from acquiring the same lock, nothing more, nothing less.

If I want to use a lock to prevent race conditions when setting a variable, I (as the programmer) need to:

  1. pass a lock to all functions targeted by threads that will want to set the variable and
  2. acquire the lock in every function and every time before I set the variable (and release it afterwards). (*)
  3. If I create even only one thread targeting a function without providing it a reference to the lock object and let it set the variable or
  4. if I set the variable via a thread whose targeted function has the lock object, but doesn't acquire it prior to the operation, I will have failed to implement thread-safe setting of the variable.

(*) The lock should be acquired as long as the variable must not be accessed by other threads. Right now, I like to compare that to a database transaction... I lock the database (~ acquire a lock) until my set of instructions is completed, then I commit (~ release the lock).


Example If I wanted to create a class whose member _value should be set in a thread-safe fashion, I would implement one of these two versions:

    class Version1:
        def __init__(self):
            self._value:int = 0
            self._lock:threading.Lock = threading.Lock()
        
        def getValue(self) -> int:
            """Getting won't be protected in this example."""
            return self._value
        
        def setValue(self, val:int) -> None:
            """This will be made thread-safe by member lock."""
            with self._lock:
                self._value = val
            
    v1 = Version1()
    t1_1 = threading.Thread(target=v1.setValue, args=(1)).start()
    t1_2 = threading.Thread(target=v1.setValue, args=(2)).start()
    
    
    class Version2:
        def __init__(self):
            self._value:int = 0
        
        def getValue(self) -> int:
            """Getting won't be protected in this example."""
            return self._value
        
        def setValue(self, val:int, lock:threading.Lock) -> None:
            """This will be made thread-safe by injected lock."""
            with self._lock:
                self._value = val
            
    v2 = Version2()
    l = threading.Lock()
    t2_1 = threading.Thread(target=v2.setValue, args=(1, l)).start()
    t2_2 = threading.Thread(target=v2.setValue, args=(2, l)).start()
  1. In Version1, I, as the class provider, can guarantee that setting _value is always thread-safe...
  2. ...because in Version2, the user of my class might pass to different lock objects to the two spawned threads and thus render the lock protection useless.
  3. If I want to give the user of my class the freedom to include the setting of _value into a larger collection of steps that should be executed in a thread-safe manner, I could inject a Lock reference into Version1's __init__ function and assign that to the _lock member. Thus, the thread-safe operation of the class would be guaranteed while still allowing the user of the class to use "her own" lock for that purpose.

A score from 0-15 will now rate how well I have (mis)understood locks... :-D

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

那片花海 2025-02-08 11:48:50
  1. 将全局变量用于锁也很常见。这取决于锁的保护。
  2. 是的,尽管有些意义。任何功能都可以使用锁定,而不仅仅是螺纹目标的功能。
  3. 如果您的意思是锁定与它保护的数据之间没有直接的链接,那是事实。但是,您可以定义一个数据结构,该数据结构包含需要保护并引用其锁定的值。
  4. 真的。尽管正如我在3中所说的那样,您可以定义包装数据和锁定的数据结构。您可以将其当作课程,并让类方法自动根据需要获取锁定。
  5. 正确的。但是,请参阅4有关如何自动化此功能的4。
  6. 正确的。
  7. 正确的。
  8. 正确的。
  9. 如果不是全球锁,请更正。
  10. 部分正确。如果您仅阅读变量,则通常还应该获得锁定。如果读取对象不是原子(例如,它是一个列表,并且您正在读取多个元素,或者您读取相同的标量对象可变时间并期望其稳定),则需要防止在您的同时修改另一个线程阅读。
  11. 正确的。
  12. 正确的。
  13. 正确的。这是我上面在3和4中描述的示例
  14. 。正确。这就是为什么13中的设计通常更好。
  15. 这很棘手,因为锁定的粒度需要反映所有需要保护的对象。您的班级仅保护该变量的分配 - 它将在完成与呼叫者提供的锁相关的所有其他步骤之前释放锁。
  1. It's also quite common to use global variables for locks. It depends on what the lock is protecting.
  2. True, although somewhat meaningless. Any function can use a lock, not just the function that's the target of a thread.
  3. If you mean there's no direct link between a lock and the data it protects, that's true. But you can define a data structure that contains a value that needs protecting and a reference to its lock.
  4. True. Although as I say in 3, you can define a data structure that packages the data and lock. You could make this a class and have the class methods automatically acquire the lock as needed.
  5. Correct. But see 4 for how you can automate this.
  6. Correct.
  7. Correct.
  8. Correct.
  9. Correct if it's not a global lock.
  10. Partially correct. You should also often acquire the lock if you're merely reading the variable. If reading the object is not atomic (e.g. it's a list and you're reading multiple elements, or you read the same scalar object variable times and expect it to be stable), you need to prevent another thread from modifying it while you're reading.
  11. Correct.
  12. Correct.
  13. Correct. This is an example of what I described above in 3 and 4.
  14. Correct. Which is why the design in 13 is often better.
  15. This is tricky, because the granularity of the locking needs to reflect all the objects that need to be protected. Your class only protects the assignment of that one variable -- it will release the lock before all the other steps associated with the caller-provided lock have been completed.
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文