单线程编程是一条顺序执行流,从上到下依次执行代码,如果遇到阻塞程序将会停滞在这里。
当然,单线程十分有限,多线程相当于使用多个执行流且互相并不会发生干扰,多线程编程包含有创建、启动线程、控制线程三个方面。
进程包含有三个特征:独立性(独立的实体,拥有自己独立的资源,并有私有地址空间,未经过允许其他进程无法直接访问地址空间)、动态性(是静态指令,通过加入时间概念,使得进程具有生命周期)、并发性(多个进程可以在单一处理器并发执行,互不干扰)。
线程:轻量级的进程,是进程的执行单元,独立、并发的执行流。互相影响:一个线程可以创建和撤销另外一个线程。共享内存、文件句柄和其他进程应有的状态。
线程共享环境有:进程代码段、进程公有数据,方便实现通信。
使用_thread和threading支持多线程。
#线程
import threading
def action(max):
for i in range(max):
print(threading.current_thread().getName() + " " + str(i) )
for i in range(100):
print(threading.current_thread().getName() + " " + str(i))
if i == 20:
t1 = threading.Thread(target = action, args = (100,))
t1.start()
t2 = threading.Thread(target = action, args = (100,))
t2.start()
print("main progess mission complete")
创建循环执行线程。多线程意义:如果不使用多线程,主程序直接调用两次action()函数,程序必须等第一次调用action()执行完成,才会执行第二次调用。
当然可以创建类继承线程thread
mport threading
class FkThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.i = 0
def run(self):
while self.i < 20:
print(threading.current_thread().getName() + " " + str(i) )
self.i += 1
for i in range(20):
print(threading.current_thread().getName() + " " + str(i) )
if i == 10:
ft1 = FkThread()
ft1.start()
ft2 = FkThread()
ft2.start()
print("main process mission complete")
CPU需要在多个线程之间切换,所以线程也将多次在运行、就绪之间转换。
新建、就绪、运行、阻塞、死亡五个状态。
程序主要为主程序,线程进入就绪状态,等待执行。
import threading
def action(max):
for i in range(max):
print(threading.current_thread().getName() + " " + str(i) )
for i in range(20):
print(threading.current_thread().getName() + " " + str(i) )
if i == 5:
threading.Thread(target = action, args= (20, )).run()
threading.Thread(target = action, args= (20, )).run()
就绪状态线程得到CPU,执行run()方法的线程,线程处于运行状态。
抛出错误或者正常结束,用is_alive()方法判断是否死亡。
join线程
将一个大问题分解成小问题,在同一个组里面分配不同线程执行。
import threading
def action(max):
for i in range(max):
print(threading.current_thread().getName() + " " + str(i) )
#启动子线程
threading.Thread(target = action, args = (100,), name = "新线程").start()
for i in range(100):
if i == 20:
jt = threading.Thread(target = action, args = (100,), name = "被join的线程")
jt.start()
#线程等jt执行结束才向下执行
jt.join()
print(threading.current_thread().name + " " + str(i))
当所有的前台线程死亡后,后台线程也会自动死亡。
import threading
def action(max):
for i in range(max):
print(threading.current_thread().name + " " + str(i))
t = threading.Thread(target = action, args = (100,), name= "后台线程")
#可以将线程设置为后台线程
t.daemon = True
t.start()
for i in range(10):
print(threading.current_thread().name + " " + str(i))
线程同步
需要考虑线程安全问题,需要设置锁。
同步锁(Lock)
class X:
def M():
self.lock.acquire()
try:
pass
finally:
self.lock.release()
一个做一个简单的账户取钱程序。
import threading
import time
class Account:
def __init__(self, account_no, balance):
self.account_no = account_no
self._balance = balance
self.lock = threading.RLock()
def getBalance(self):
return self._balance
def draw(self, draw_amount):
self.lock.acquire()
try:
if self._balance >= draw_amount:
print(threading.current_thread().name\
+ "取钱成果,钞票:" + str(draw_amount))
time.sleep(0.01)
self._balance -= draw_amount
print("\t 余额为:" + str(self._balance))
else:
print(threading.current_thread().name \
+ "取钱失败,余额不足")
finally:
self.lock.release()
def draw(account, draw_amount):
account.draw(draw_amount)
acct = Account("1234567", 100)
threading.Thread(name= "甲", target = draw , args =(acct, 800)).start()
threading.Thread(name= "乙", target = draw , args =(acct, 800)).start()
采用策略有:1.不要对线安全类所有方法都进行同步,只对那些改变竞争资源的方法同步。2.可变类有两种运行环境,单线程环境和多线程环境。
这个需要注意,时序和定时锁场景。
使用队列控制线程通信
import threading
import time
import queue
def product(bq):
str_tuple = ("python", "Kotlin", "Swift")
for i in range(9999):
print(threading.current_thread().name + "生产者准备生产")
time.sleep(0.2)
#将生产完产品放入队列,如果队列满了阻塞
bq.put(str_tuple[i % 3])
print(threading.current_thread().name + "生产者生产完成")
def consume(bq):
while True:
print(threading.current_thread().name + "消费者准备消费")
time.sleep(0.2)
#取出队列中的产品,如果队列为空,阻塞
t = bq.get()
print(threading.current_thread().name + "消费者消费[%s]完成" % t)
bq = queue.Queue(maxsize = 1)
threading.Thread(target = product, args = (bq,)).start()
threading.Thread(target = product, args = (bq,)).start()
threading.Thread(target = product, args = (bq,)).start()
threading.Thread(target = consume, args = (bq,)).start()
用Event可以实现事件以控制线程通信效果
import threading
import time
event = threading.Event()
def cal(name):
print("%s 启动" % threading.currentThread().getName())
print("%s 准备开始计算" % name)
event.wait()
print("%s 收到通知" % threading.currentThread().getName())
print("%s 正式开始计算" % name)
threading.Thread(target = cal, args = ("甲",)).start()
threading.Thread(target = cal, args = ("乙",)).start()
time.sleep(2)
print("--------------------")
print("main thread event")
event.set()
线程池基类是concurrent.futures的Executor。使用线程池或者进程池并发编程,将相应的task函数提交给线程池或进程池就好。
程序将task函数提交到线程池,submit返回一个future对象,获取线程任务函数的返回值。
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def action(max):
my_sum = 0
for i in range(max):
print(threading.current_thread().name + " " + str(i))
my_sum += i
return my_sum
with ThreadPoolExecutor(max_workers = 2) as pool:
future1 = pool.submit(action, 10)
future2 = pool.submit(action, 20)
def get_result(future):
print(future.result())
print(future1.done())
time.sleep(3)
print(future2.done())
future1.add_done_callback(get_result)
用with上下文管理器进行分析,shoutdown节省关闭代码。
程序启动两个进程,一个是父进程,一个是子进程。
import multiprocessing
import os
def action(max):
for i in range(max):
print("(%s)子进程, 父进程(%s) %d" %(os.getpid(), os.getppid(),i))
if __name__ == "__main__":
for i in range(100):
print("(%s)子进程, %d" %(os.getpid(), i))
if i == 20:
mp1 = multiprocessing.Process(target = action, args = (100, ))
mp1.start()
mp2 = multiprocessing.Process(target = action, args = (100, ))
mp2.start()
mp2.join()
print("main process mission complete")
当然,进程同样可以向线程一样进行继承。
import multiprocessing
import os
class MyProcess(multiprocessing.Process):
def __init__(self, max):
self.max = max
super().__init__()
def run(self):
for i in range(self.max):
print("(%s)子进程, 父进程(%s) %d" %(os.getpid(), os.getppid(),i))
if __name__ == "__main__":
for i in range(100):
print("(%s)子进程, %d" %(os.getpid(), i))
if i == 20:
mp1 = MyProcess(100)
mp1.start()
mp2 = MyProcess(100)
mp2.start()
mp2.run()
print("main process mission complete")
python支持三种方法启动进程,spawn:启动全新的python解释器。fork:子进程继承父进程所有资源,子进程基本等于父进程。forkserver:启动一个服务器进程。显示启动进程。
import multiprocessing
import os
def foo(q):
print("被启动新进程(%s) %d" %(os.getpid(), i))
q.put("python")
if __name__ == '__main__':
ctx = multiprocessing.get_context('fork')
q = ctx.Queue()
mp = ctx.Process(target = foo, args = (q, ))
mp.start()
print(q.get())
mp.join()
windows使用
multiprocessing.get_context('fork')
函数获取context进行内容设置。