Python基础之多线程
本节将介绍Python中的多线程技术以及线程同步相关的内容。
继承 threading.Thread
最正式的一种方式是写一个多线程类,并继承 threading.Thread,重写run方法。例如:
import threading
import time
class MyThread(threading.Thread):
def __init__(self, id, delay):
self.__id = id
self.__delay = delay
super().__init__()
def run(self):
for i in range(3):
print("%s hello %d ... %s" % (self.__id, i, time.ctime(time.time())))
time.sleep(self.__delay)
t1 = MyThread(1, 1)
t2 = MyThread(2, 2)
t1.start()
t2.start()
t1.join()
t2.join()
print("main end.")
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_09/thread_01.py
1 hello 0 ... Tue Sep 1 17:30:58 2020
2 hello 0 ... Tue Sep 1 17:30:58 2020
1 hello 1 ... Tue Sep 1 17:30:59 2020
1 hello 2 ... Tue Sep 1 17:31:00 2020
2 hello 1 ... Tue Sep 1 17:31:00 2020
2 hello 2 ... Tue Sep 1 17:31:02 2020
main end.
Process finished with exit code 0
多线程函数
如果不想创建多线程类,可以定义一个多线程函数。例如:
import threading
import time
def say_hello(id, delay):
for i in range(3):
print("%s hello %d ... %s" % (id, i, time.ctime(time.time())))
time.sleep(delay)
t1 = threading.Thread(target=say_hello, args=(1, 1))
t2 = threading.Thread(target=say_hello, args=(2, 2))
t1.start()
t2.start()
t1.join()
t2.join()
print("main end.")
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_09/thread_02.py
1 hello 0 ... Tue Sep 1 17:32:04 2020
2 hello 0 ... Tue Sep 1 17:32:04 2020
1 hello 1 ... Tue Sep 1 17:32:05 2020
1 hello 2 ... Tue Sep 1 17:32:06 2020
2 hello 1 ... Tue Sep 1 17:32:06 2020
2 hello 2 ... Tue Sep 1 17:32:08 2020
main end.
Process finished with exit code 0
通过 threading.Lock() 加锁
这是最基本的一种加锁方式。例如:
import threading
import time
LOCK = threading.Lock()
def say_hello(id, delay):
LOCK.acquire()
for i in range(5):
print("%s hello %d ... %s" % (id, i, time.ctime(time.time())))
time.sleep(delay)
LOCK.release()
t1 = threading.Thread(target=say_hello, args=(1, 1))
t2 = threading.Thread(target=say_hello, args=(2, 2))
t1.start()
t2.start()
t1.join()
t2.join()
print("main end.")
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_09/thread_03.py
1 hello 0 ... Tue Sep 1 17:34:43 2020
1 hello 1 ... Tue Sep 1 17:34:44 2020
1 hello 2 ... Tue Sep 1 17:34:45 2020
1 hello 3 ... Tue Sep 1 17:34:46 2020
1 hello 4 ... Tue Sep 1 17:34:47 2020
2 hello 0 ... Tue Sep 1 17:34:48 2020
2 hello 1 ... Tue Sep 1 17:34:50 2020
2 hello 2 ... Tue Sep 1 17:34:52 2020
2 hello 3 ... Tue Sep 1 17:34:54 2020
2 hello 4 ... Tue Sep 1 17:34:56 2020
main end.
Process finished with exit code 0
可见,线程2必须等线程1执行完成后才能开始执行。
threading.Lock() 有一个限制,就是acquire以后不能再有acquire,必须release后才能acquire。如果没有release但又acquire,那么程序将被block。例如:
import threading
import time
LOCK = threading.Lock()
def say_hello(id, delay):
LOCK.acquire()
LOCK.acquire() # Thread is blocked here, cannot invoke acquire 2 times unless release one
for i in range(5):
print("%s hello %d ... %s" % (id, i, time.ctime(time.time())))
time.sleep(delay)
LOCK.release()
LOCK.release()
t1 = threading.Thread(target=say_hello, args=(1, 1))
t2 = threading.Thread(target=say_hello, args=(2, 2))
t1.start()
t2.start()
t1.join()
t2.join()
print("main end.")
通过 threading.RLock() 加锁
threading.RLock()可以解决多次acquire的问题。例如:
import threading
import time
LOCK = threading.RLock()
def say_hello(id, delay):
LOCK.acquire()
LOCK.acquire() # Thread isn't blocked here because use RLock instead of Lock
for i in range(5):
print("%s hello %d ... %s" % (id, i, time.ctime(time.time())))
time.sleep(delay)
LOCK.release()
LOCK.release() # Invoke acquire 2 times, but invoked release 2 time, so thread 2 can go into say_hello()
t1 = threading.Thread(target=say_hello, args=(1, 1))
t2 = threading.Thread(target=say_hello, args=(2, 2))
t1.start()
t2.start()
t1.join()
t2.join()
print("main end.")
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_09/thread_06.py
1 hello 0 ... Tue Sep 1 17:47:28 2020
1 hello 1 ... Tue Sep 1 17:47:29 2020
1 hello 2 ... Tue Sep 1 17:47:30 2020
2 hello 0 ... Tue Sep 1 17:47:31 2020
2 hello 1 ... Tue Sep 1 17:47:33 2020
2 hello 2 ... Tue Sep 1 17:47:35 2020
main end.
Process finished with exit code 0
注意,acquire和release必须成对出现,否则也要造成block。例如:
import threading
import time
LOCK = threading.RLock()
def say_hello(id, delay):
LOCK.acquire()
LOCK.acquire() # Thread isn't blocked here because use RLock instead of Lock
for i in range(3):
print("%s hello %d ... %s" % (id, i, time.ctime(time.time())))
time.sleep(delay)
LOCK.release()
# LOCK.release() # Invoke acquire 2 times, but invoked release 1 time, so thread 2 cannot go into say_hello()
t1 = threading.Thread(target=say_hello, args=(1, 1))
t2 = threading.Thread(target=say_hello, args=(2, 2))
t1.start()
t2.start()
t1.join()
t2.join()
print("main end.")
运行结果:
程序block,无法继续执行。
自定义阻塞式队列
通过Condition和RLock,可以创建一个自定义的阻塞式队列。例如:
import threading
import time
class MyBlockingQueue:
def __init__(self, capacity):
if capacity <= 0:
self.__size = 10 # Default size is 10
else:
self.__capacity = capacity
self.__lock = threading.RLock()
self.__not_full = threading.Condition(self.__lock)
self.__not_empty = threading.Condition(self.__lock)
self.__data = []
def offer(self, e):
self.__lock.acquire()
try:
if len(self.__data) >= self.__capacity:
self.__not_full.wait()
self.__data.append(e)
print("Append data: %r" % e)
self.__not_empty.notify()
except Exception as e:
raise e
finally:
self.__lock.release()
def poll(self):
self.__lock.acquire()
try:
if len(self.__data) == 0:
self.__not_empty.wait()
e = self.__data.pop(0)
self.__not_full.notify()
return e
except Exception as e:
raise e
finally:
self.__lock.release()
class ReadDataThread(threading.Thread):
def __init__(self, queue):
self.__queue = queue
super().__init__()
def run(self):
e = self.__queue.poll()
print("Read data: %r" % e)
class WriteReadDataThread(threading.Thread):
def __init__(self, queue, data):
self.__queue = queue
self.__data = data
super().__init__()
def run(self):
self.__queue.offer(self.__data)
q = MyBlockingQueue(2)
wlist = []
rlist = []
for i in range(4):
wlist.append(WriteReadDataThread(q, i))
rlist.append(ReadDataThread(q))
for w in wlist:
w.start()
for r in rlist:
r.start()
print("Main end.")
运行结果:
D:\work\python_workspace\python_study\venv\Scripts\python.exe D:/work/python_workspace/python_study/basic_09/thread_07.py
Append data: 0
Append data: 1
Read data: 0
Append data: 2
Read data: 1
Read data: 2
Append data: 3
Read data: 3
Main end.
Process finished with exit code 0