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())

浙公网安备 33010602011771号