Console program checks if number is prime. Why does `threading.Lock()` cause it to fail for products of non-tiny primes?

huangapple go评论65阅读模式
英文:

Console program checks if number is prime. Why does `threading.Lock()` cause it to fail for products of non-tiny primes?

问题

我正在阅读关于Python书籍中并行编程的章节。以下代码基于关于模块 threading 的示例。这个特定示例的重点是使用 threading.Lock()。代码也可以在 这里(GitHub 上)找到。没有锁的代码在 这里

import threading


class PrimeThread(threading.Thread):

    lock = threading.Lock()

    def __init__(self, n):
        super().__init__()
        self.n = n

    def run(self):
        if self.n < 2:
            with PrimeThread.lock:
                print('▪ 这不是大于1的整数。')
            return
        i = 2
        while i ** 2 <= self.n:
            remainder = self.n % i
            quotient = self.n // i
            if remainder == 0:  # 如果 n 可以被 i 整除
                with PrimeThread.lock:
                    print(
                        f'▪ {self.n} 不是素数,因为它等于 {i} * {quotient}。'
                    )
                return
            i += 1
        with PrimeThread.lock:
            print(f'▪ {self.n} 是素数。')


my_threads = []

user_input = input('▶ ')

while user_input != 'q':
    try:
        n = int(user_input)  # 如果字符串不是整数,会引发 ValueError
        thread = PrimeThread(n)
        my_threads.append(thread)
        thread.start()
    except ValueError:
        with PrimeThread.lock:
            print('▪ 这不是整数。')

    with PrimeThread.lock:
        user_input = input('▶ ')

for t in my_threads:
    t.join()

最初版本的问题在于输出可能会干扰新输入。锁的目的是确保输出和输入行不会混在一起。

在这种情况下,您可能需要对输入和输出的同步进行更多控制,以确保不会出现混淆。您可以使用线程安全的队列来缓冲输出,然后在适当的时候输出到控制台。这将确保输入和输出不会混在一起。

关于您的更一般问题,用控制台演示线程和锁是否必要,这取决于您的教育目标。如果您的目标是演示多线程编程中可能发生的竞争条件和需要锁来处理它们,那么控制台可能是一个不错的方式。但如果您更关心多线程在实际应用中的用途,那么可以考虑使用更实际的示例来说明。

请注意,多线程编程涉及复杂性,可能会有潜在的难以调试的问题。确保在教育目标和示例中清晰地传达这些概念是很重要的。

英文:

I am reading the chapter about parallel programming in a Python book.<br>
The following code is based on an example about the module threading.<br>
The point of this particular example is the use of threading.Lock().<br>
The code can also be found here (on GitHub).
The code without the lock is here.

import threading


class PrimeThread(threading.Thread):

    lock = threading.Lock()

    def __init__(self, n):
        super().__init__()
        self.n = n

    def run(self):
        if self.n &lt; 2:
            with PrimeThread.lock:
                print(&#39;▪ That is not an integer greater 1.&#39;)
            return
        i = 2
        while i ** 2 &lt;= self.n:
            remainder = self.n % i
            quotient = self.n // i
            if remainder == 0:  # if n is divisible by i
                with PrimeThread.lock:
                    print(
                        f&#39;▪ {self.n} is not prime, &#39;
                        f&#39;because it is {i} * {quotient}.&#39;
                    )
                return
            i += 1
        with PrimeThread.lock:
            print(f&#39;▪ {self.n} is prime.&#39;)


my_threads = []

user_input = input(&#39;▶ &#39;)

while user_input != &#39;q&#39;:
    try:
        n = int(user_input)  # raises ValueError if string is not integer
        thread = PrimeThread(n)
        my_threads.append(thread)
        thread.start()
    except ValueError:
        with PrimeThread.lock:
            print(&#39;▪ That is not an integer.&#39;)

    with PrimeThread.lock:
        user_input = input(&#39;▶ &#39;)

for t in my_threads:
    t.join()

The problem with the initial version was, that the output could interfere with the new input:

▶ 126821609265383
▶ 874496478251477▪ 126821609265383 is prime.

▶ ▪ 874496478251477 is not prime, because it is 23760017 * 36805381.

The lock is supposed to achieve, that it looks like this:

▶ 126821609265383
▶ 874496478251477
▪ 126821609265383 is prime.
▪ 874496478251477 is not prime, because it is 23760017 * 36805381.

Maybe it does achieve that. (Hard to test with inputs that allow a fast answer.)<br>
But now the program does not return anything for many inputs with 7 or more digits.<br>
4374553 (prime) sometimes works.
But 76580839 (prime) and 67898329 (2953 &middot; 22993) will fail.

How can I use the lock to prevent the mixing of input and output lines, and still get the result printed, when the calculation is finished?

<s>More generally, I would like to ask, if the console is necessary to illustrate the point of threads and locks.
Could those also be illustrated by just looping over a list of numbers?</s><br>
(I should probably bring that up on CodeReview.)


Edit: I just realized, that the missing output can be triggered, by entering some easy input.<br>
In this case the program was stuck after entering 76580839.<br>
But entering 123 caused both answers to appear:

Console program checks if number is prime. Why does `threading.Lock()` cause it to fail for products of non-tiny primes?

But of course the missing answer can only be released with this trick, when it already exists. In this case the calculation for 126821609265383 was not yet finished, when 123 was entered, but a bit later it was released by entering 345. Surprisingly now the output for 345 is stuck:

Console program checks if number is prime. Why does `threading.Lock()` cause it to fail for products of non-tiny primes?

My question remains the same:<br>
What needs to be changed, to make the program behave normally for non-easy inputs?<br>
(The way it already works for easy inputs.)

As mentioned above: This is an example problem about threading.<br>
The prime check is just a placeholder for a calculation that takes time and resources.

答案1

得分: 0

这是你要翻译的内容:

"当在主线程调用input()之前获得锁定时,无法完成你想要的操作。因为工作线程无法获得锁定,所以无法打印结果。对于一个小整数,工作线程可以打印结果,因为它在主线程尝试获得锁定之前获得了锁定。CPython的当前实现在发生上下文切换之前会给一个新创建的线程短暂的时间(5毫秒)。

你可以通过在代码开头添加以下代码来实验这个特性:

import sys
sys.setswitchinterval(20) # 单位是秒
```"

<details>
<summary>英文:</summary>

It&#39;s impossible to do what you want. When the lock is acquired in the main thread before the ```input()``` is called, a worker thread can&#39;t print a result because it can&#39;t acquire the lock. For a small integer, a worker thread can print a result because it acquires the lock before the main thread tries to acquire the lock. The current implementation of CPython gives a short time(5ms) to a newly created thread before a context switch occurs.

You can experiment this feature by adding the following code at the beginning of your code.

import sys
sys.setswitchinterval(20) # In seconds



</details>



huangapple
  • 本文由 发表于 2023年7月20日 20:11:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/76729716.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定