来点干货:Python并发编程探究(面试挂在这了55555)

后台进程

我的博客原文链接!!!!观看效果更好
使用threading模块来进行新线程的创建,如果默认情况下的话,是会守护子线程的,也就是,哪怕主线程已经结束了,子线程没有退出,python的解释器就不会退出:

import time
from threading import Thread
def countdown(n):
    while n>0:
        print('T-minus',n)
        n-=1
        time.sleep(5)

t=Thread(target=countdown,args=(10,))
t.start()
for i in range(4):
    print('主线程')

输出为:

T-minus主线程
10
主线程
主线程
主线程
T-minus 9
。。。(省略)

可以看到,再主线程打印了4次"主线程"之后,还是会等子线程打印"T-minus "打完才退出。

而我们可以通过设置daemon=True来使子线程为后台进程,我们看看会有什么不同:

t=Thread(*target*=countdown,*args*=(10,),*daemon*=True)

T-minus 10
主线程
主线程
主线程
主线程

在主线程退出之后,子线程也退出了。

后台线程的责任是为整个主线程提供服务,如保持网络连接(发送keep-alive心跳包),负责内存管理与垃圾回收(实际上JVM就是这样做的). 因此这些线程与实际提供应用服务的线程有了逻辑上的”前/后”的概念,而如果主线程已经退出,那么后台线程也没有存在的必要.
如果没有这一机制,那么我们在主线程完成之后,还必须逐个地检查后台线程,然后在主线程退出之前,逐个地关闭它们. 有了前后线程的区分, 我们只需要负责管理前台线程, 完成主要的逻辑处理之后退出即可.

来源:stakoverflow

利用循环条件控制线程

上述后台线程的方法帮我们解决了部分线程退出的问题,但是我们还是没法控制它,比如让它在特定的点退出,下面是一个写法,利用了类——相当于给线程每次一个查询的操作:

class CountdownTask:
    def __init__(self):
        self._running = True

    def terminate(self):
        self._running = False

    def run(self, n):
        while self._running and n > 0:
            print('T-minus', n)
            n -= 1
            time.sleep(5)

c = CountdownTask()
t = Thread(target=c.run, args=(10,))
t.start()
c.terminate() # Signal termination
t.join()      # Wait for actual termination (if needed)

在这里面,利用这个类的terminate函数,以及while循环中的条件,实现了我们的手动主动退出。

利用超时机制控制线程

但是还存在一个问题:

上述利用循环控制线程的方法的确可以做到对特定情况下线程的退出,但是如果是开启一个网络服务器这样的呢,根本不会循环判断,怎么办:

如果线程执行一些像I/O这样的阻塞操作,那么通过轮询来终止线程将使得线程之间的协调变得非常棘手。比如,如果一个线程一直阻塞在一个I/O操作上,它就永远无法返回,也就无法检查自己是否已经被结束了。要正确处理这些问题,你需要利用超时循环来小心操作线程。 例子如下:

class IOTask:
    def terminate(self):
        self._running = False

    def run(self, sock):
        # sock is a socket
        sock.settimeout(5)        # Set timeout period
        while self._running:
            # Perform a blocking I/O operation w/ timeout
            try:
                data = sock.recv(8192)
                break
            except socket.timeout:
                continue
            # Continued processing
            ...
        # Terminated
        return

利用socket的超时函数,来实现自我检查

GIL锁(初了解)

  • GIL 保证CPython进程中,只有一个线程执行字节码。甚至是在多核CPU的情况下,也只允许同时只能有一个CPU 上运行该进程的一个线程。
  • CPython中
    1. IO密集型,某个线程阻塞,就会调度其他就绪线程;
    2. CPU密集型,当前线程可能会连续的获得GIL,导致其它线程几乎无法使用CPU。
  • 在CPython中由于有GIL存在,IO密集型,使用多线程较为合算;CPU密集型,使用多进程,要绕开GIL。

print()等就是IO输出,应该避免,否则线程会阻塞,会释放GIL锁,其他线程被调度。

由于全局解释锁(GIL)的原因,Python 的线程被限制到同一时刻只允许一个线程执行这样一个执行模型。所以,Python 的线程更适用于处理I/O和其他需要并发执行的阻塞操作(比如等待I/O、等待从数据库获取数据等等),而不是需要多处理器并行的计算密集型任务。

event对象协调线程运行

例子很好,基本将通了

只是上述链接里面的例子,注意看清楚他是初始化了三个一模一样的线程,然后再event.set()之前,都是被event.wait()了,处于阻塞状态。

event对象的一个重要特点是当它被设置为真时会唤醒所有等待它的线程。如果你只想唤醒单个线程,最好是使用信号量或者 Condition 对象来替代。考虑一下这段使用信号量实现的代码:

# Worker thread
def worker(n, sema):
    # Wait to be signaled
    sema.acquire()

    # Do some work
    print('Working', n)

# Create some threads
sema = threading.Semaphore(0)
nworkers = 10
for n in range(nworkers):
    t = threading.Thread(target=worker, args=(n, sema,))
    t.start()

关于信号量的,下一次再写 2021.2.22

猜你喜欢

转载自blog.csdn.net/QinZheng7575/article/details/113961678