Python多线程编程

一、线程的基本操作

1、单线程:起一个线程,执行分支任务,这是一个很简单的方法,但是一旦启动一个线程,该线程会由操作系统来管理,独立执行直到目标函数返回。比如执行以下代码:

import time
import threading


def fun(n):
    while n > 0:
        n -= 1
        print("thread name is %s" % threading.current_thread())
        time.sleep(1)


t = threading.Thread(target=fun, args=(5,))
t.start()
print("main thread is %s" % threading.current_thread()

这里面主线程先执行完,启动的线程由操作系统接管,独立执行。所以打印如下:

thread name is <Thread(Thread-1, started 14484)>main thread is <_MainThread(MainThread, started 9572)>

thread name is <Thread(Thread-1, started 14484)>

thread name is <Thread(Thread-1, started 14484)>

thread name is <Thread(Thread-1, started 14484)>

thread name is <Thread(Thread-1, started 14484)>

如果想让主线程等待子线程执行结束后再执行,可使用t.join()方法,这样主线程在t.join()处等待子线程执行结束后执行后续代码。

如果创建线程对象的时候指定了deamon参数,t = threading.Thread(target=func, args=(10, ), daemon=True),子线程为后台线程,后台线程无法等待,主线程结束后自动终止。

2、多线程:

2.1 多线程编程的锁机制

在多线程编程中, 在线程编程中,如果没有加锁,多线程情况下,可能会出现线程竞争,导致n[1]在某个线程中加1的前后差值大于一的情况

import time
import threading


def add_one(n):
    print("thread name is %s, number is %s" % (threading.current_thread(), n))
    time.sleep(1)
    n[1] += 1
    print("thread name is %s, number is %s" % (threading.current_thread(), n))


n = [0, 0, 0]
for i in range(20):
    t = threading.Thread(target=add_one, args=(n, ))
    t.start()
print("main thread is %s" % threading.current_thread())

比如出现以下情况: 

thread name is <Thread(Thread-10, started 14804)>, number is [0, 0, 0]

thread name is <Thread(Thread-10, started 14804)>, number is [0, 8, 0]

在线程10中,n += 1 前后,n的值变化了8. 为了解决这种多线程竞争导致的全局变量修改不符合预期的情况,我们可以用线程锁。但是由于锁的等待,多线程执行的速度也会降低。

import time
import threading

Lock = threading.Lock()

def add_one(n):
    # with threading.Lock():  如果在这里添加互斥锁,其实每个子线程都会有一个锁,并不能在线程之间起到数据保护的作用,应该使用全局互斥锁
    with Lock:
        print("thread name is %s, number is %s" % (threading.current_thread(), n))
        time.sleep(1)
        n[1] += 1
        print("thread name is %s, number is %s" % (threading.current_thread(), n))


n = [0, 0, 0]
for i in range(20):
    t = threading.Thread(target=add_one, args=(n, ))
    t.start()
print("main thread is %s" % threading.current_thread())

2.2 多线程编程中处理线程之间的依赖

        在多线程编程过程中,有时候某个线程依赖于另一个线程的状态,但是我们知道,线程被操作系统管理,这个时候我们可以使用threading 模块中的一些对象来控制:

(1)threading库中的Event对象,这个对象是可以由线程设置的信号标志,在多线程编程中,可以设置线程等待Event对象状态,也可以设置Event对象的状态,比如:

event = threading.Event()   # 创建threading.Event()对象
event.is_set()   # 获取event的设置值,默认为False
event.set()      # 设置event的值为True
event.clear()    # 设置event的值为False
event.wait()     # 等到event的值被设为True就执行

比如:多线程编程的红绿灯的例子如下

import time
import threading

traffic_event = threading.Event()

def traffic_light(event):
    event.set()
    count = 0
    while True:
        if 0 <= count < 5:
            event.set()
            print("light is red, car stop!")
        elif 5 <= count < 10:
            event.clear()
            print("light is green, car pass")
        count += 1
        if count >= 10:
            count = 0
        time.sleep(1)


def car(name, event):
    while True:
        if not event.is_set():
            print("%s start moving" % name)
            event.wait()
            print("%s stop, waiting for green light" % name)


t1 = threading.Thread(target=traffic_light, args=(traffic_event, ))
t2 = threading.Thread(target=car, args=("toyota", traffic_event, ))
t1.start()
t2.start()

在线程中,加入对Event状态进行判断和修改,达到一个线程等待另一个线程的执行。Event对象的一个重要特点就是设置为真时唤醒所有等待Event状态的进程。如果想要唤醒单个或者一定数量的线程。可以使用condition对象来替代。

(2)Condition对象进行线程控制和源码解析

前面说了,Condition可以唤醒一定数量的线程,从而达到线程的控制,Condition的常用用法如下:

condition = threading.Condition(lock=None)   # 创建Condition对象  参数可以不传

condition.acquire()    # 加锁
condition.release()    # 解锁

condition.wait(timeout=None)                 # 阻塞,直到有调用notify(),或者notify_all()时再触发
condition.wait_for(predicate, timeout=None)  # 阻塞,等待predicate条件为真时执行

condition.notify(n=1)        # 通知n个wait()的线程执行, n默认为1
condition.notify_all()       # 通知所有wait着的线程执行
with condition:              # 支持with语法,不必每次手动调用acquire()/release()

下面举一个用Condition做多线程控制的例子:

import threading
import time

condition = threading.Condition()    # 创建condition对象


def func():
    condition.acquire()    # 如果没有with语句,必写这句,否者报错
    condition.wait()       # 阻塞,等待其他线程调用notify()
    print("in threading %s" % threading.current_thread())
    condition.release()    # 与acquire()成对出现


# 启10个线程
for i in range(10):
    t = threading.Thread(target=func, args=())
    t.start()

time.sleep(5)

condition.acquire()
condition.notify(2)        # 通知两个线程执行
condition.release()

运行结果为:

in threading <Thread(Thread-1, started 4304)>

in threading <Thread(Thread-2, started 10384)>

其他所有进程均在等待中,就算前两个运行的进程都结束了,其他进程也都在等待。现在看下Condition是如何控制线程的:

#Condition本身就是一个锁,如果实例化不指定锁的类型时,默认使用可重入锁,可重入锁的重要特性是一个线程中可以重复获取锁,防止线程互锁的现象发生
class Condition:
    def __init__(self, lock=None):
        if lock is None:
            lock = RLock()
        self._lock = lock
        # Export the lock's acquire() and release() methods
        self.acquire = lock.acquire
        self.release = lock.release
        # If the lock defines _release_save() and/or _acquire_restore(),
        # these override the default implementations (which just call
        # release() and acquire() on the lock).  Ditto for _is_owned().
        try:
            self._release_save = lock._release_save
        except AttributeError:
            pass
        try:
            self._acquire_restore = lock._acquire_restore
        except AttributeError:
            pass
        try:
            self._is_owned = lock._is_owned
        except AttributeError:
            pass
        self._waiters = _deque()                  #等待的线程队列

通过在线程中加入Condition.wait()方法,可以使线程等待其他线程调用notify()方法:

首先来看Conditon.wait()方法:

def wait(self, timeout=None):
    if not self._is_owned():
        raise RuntimeError("cannot wait on un-acquired lock")
    waiter = _allocate_lock()                                    #返回一个初始化好且未上锁的对象waiter,我猜测锁的类型为互斥锁
    waiter.acquire()                                         #waiter上锁
    self._waiters.append(waiter)                                                                 
    saved_state = self._release_save()
    gotit = False
    try:    # restore state no matter what (e.g., KeyboardInterrupt)
        if timeout is None:
            waiter.acquire()                      #这边如果没有设置超时,waiter会一直等待,我们在线程中调用Condition.wait函数,这个时候
                                          如果没有设置超时,线程会一直在等待获取锁,操作系统会切换其他进程,这个时候除非锁waiter被
                                          释放了,不然线程一直卡死
            gotit = True                       #如果其他线程调用了notify函数,waiter线程将继续走下去。
        else:
            if timeout > 0:
                gotit = waiter.acquire(True, timeout)
            else:
                gotit = waiter.acquire(False)
        return gotit
    finally:
        self._acquire_restore(saved_state)
        if not gotit:
            try:
                self._waiters.remove(waiter)
            except ValueError:
                pass

所以多线程的阻塞和切换是靠锁的等待来实现的,和协程的yield等待方式不一样。

再看下Condition.notify()函数:

def notify(self, n=1):
   if not self._is_owned():
        raise RuntimeError("cannot notify on un-acquired lock")
    all_waiters = self._waiters
    waiters_to_notify = _deque(_islice(all_waiters, n))    #获取前n个等待进程的锁
    if not waiters_to_notify:                              #如果没有等待进程,不做任何操作
        return
    for waiter in waiters_to_notify:                    #释放n个等待进程的锁,然后移除控制,这里的移除控制就是将已释放的线程锁从Condition对象中self._waiter队列移除
        waiter.release()
        try:
            all_waiters.remove(waiter)
        except ValueError:
            pass

(3) 多线程编程中的信号量semaphore,信号量用于控制释放固定量的线程。比如启动100个线程,信号量的控制值设为5,那么前5个线程拿到信号量之后,其余线程只能阻塞,等到这5个线程释放信号量锁之后才能去拿锁。

(4)多线程编程中的Barriers字面意思是“屏障”,是Python线程(或进程)同步原语。每个线程中都调用wait()方法,当其中一个线程执行到wait方法处会立阻塞;一直等到所有线程都执行到wait方法处,所有线程再继续执行。

 
import time
import threading

Lock = threading.Lock()


def add_one(n):
    # with threading.Lock():  如果在这里添加互斥锁,其实每个子线程都会有一个锁,并不能在线程之间起到数据保护的作用,应该使用全局互斥锁
    with Lock:
        print("thread name is %s, number is %s" % (threading.current_thread(), n))
        time.sleep(1)
        n[1] += 1
        print("thread name is %s, number is %s" % (threading.current_thread(), n))


n = [0, 0, 0]
for i in range(20):
    t = threading.Thread(target=add_one, args=(n, ))
    t.start()
print("main thread is %s" % threading.current_thread())
posted @ 2022-10-28 17:55  Kevin_zsq  阅读(741)  评论(0)    收藏  举报