1. 初识多线程
课前预习
_thread(低级模块) 和 threading(高级模块,他是对_thraed的封装)
进程:数学题 线程:解题方法
1.1 使用threading创建线程
threading提供了Thread(模块)这个方法创建一个线程对象
import threading,time #threading即线程
#一个线程函数
def process():
for i in range(3):
time.sleep(1)
print("线程的名字是:%s"%(threading.current_thread().name)) #threading.current_thread() 方法返回一个线程
if __name__ == '__main__':
print("---主线程开始---")
#创建4个线程,并且将其存入列表中
thteads = [threading.Thread(target=process) for i in range(4)] #threading.Thread(target=process) 创建线程
#启动线程
print("启动线程")
for t in thteads:
t.start()
#结束线程
print("结束线程")
for t in thteads:
t.join()
print("---主线程结束---")
以上代码创建了4个线程,然后分别用for循环执行start()和join()方法,每个子线程分别执行输出三次。所以定义的process函数的作用就在于规范线程的执行状况。
thteads = [threading.Thread(target=process) for i in range(4)]
这一步中的 threading.Thread(target=process) 其实就是直接实例化一个线程对象,而threading.Thread即为创建线程的模块,相当于我们容易理解的:
t = threading.Thread(target=process) # t 就是实例化的线程对象
类比进程中使用Process创建进程对象。
而对于函数:
#一个线程函数
def process():
for i in range(3):
time.sleep(1)
print("线程的名字是:%s"%(threading.current_thread().name)) #threading.current_thread() 方法返回一个线程
就是编写自己对所创建的线程的要求和规范,作用类似于threading.Thread模块中的run()方法,所以下文中的通过继承threading.Thread类,创建SubThread子类,然后利用SubThread子类构建线程的方式,就省去了当前代码中再“特意”去编写一个def process函数规范线程的操作。(要求每个线程都执行3次)
1.2 使用Thread子类创建线程
通过继承来创建线程,联系的进程中使用Process。
import threading,time
#继承threading.Thread模块,自定义一个线程子类
class SubThreads(threading.Thread):
#重写threading.Thread中关于线程的运行方法run();使得每个线程执行三遍
def run(self):
for i in range(3):
time.sleep(1) #休眠1s
msg = ("子线程"+self.name+"现在执行第%s次"%str(i))
print(msg)
if __name__ == '__main__':
print("---主线程开始执行---")
t1 = SubThreads() #实例化线程t1
t2 = SubThreads()
print("子线程开始启动...")
t1.start() #启动子线程t1
t2.start()
t1.join() #等待子线程结束
t2.join()
print("---主线程结束---")
如此便把1.1中def process的规范重写到run()方法中。
2.线程间的通信
线程间的通信:
from threading import Thread
import time
#规范一个子线程1
def pluse():
print("---子线程1开始执行---")
global g_num #定义一个全局变量
g_num += 50
print("执行子线程1,使g_num的值变为:%s"%(g_num))
print("---子线程1结束---")
#规范一个子线程2
def minse():
print("---子线程2开始执行---")
global g_num #定义一个全局变量
g_num += 50
print("执行子线程2,使g_num的值变为:%s"%(g_num))
print("---子线程2结束---")
g_num = 100 #定义一个全局变量
if __name__ == '__main__':
print("---主线程启动---")
print("初始时g_num的值为:%s"%(g_num))
t1 = Thread(target=pluse) #实例化子线程1
t2 = Thread(target=minse)
print("---子线程启动---")
t1.start()
t2.start()
t1.join()
t2.join()
print("---主线程结束---")
进程间的通信:
from multiprocessing import Process
#子进程1
def plus():
print("---子进程1开始执行---")
global g_num #声明全局变量
g_num += 50
print("在子进程1下:g_num = %d"%(g_num))
print("---子进程1结束运行---")
#子进程2
def minus():
print("---子进程2开始执行---")
global g_num #声明全局变量
g_num -= 50
print("在子进程2下:g_num = %d"%(g_num))
print("---子进程2结束运行---")
g_num = 100 #赋值全局变量
if __name__ == '__main__':
print("---主进程启动---")
print("在主进程运行中,g_num = %d"%(g_num))
child1 = Process(target=plus) #实例化子进程1
child2 = Process(target=minus) # 实例化子进程2
child1.start() #启动子进程1
child2.start()
child1.join() #等待子进程结束
child2.join()
print("---主进程结束---")
对比如上两个程序可知,对于全局变量,子线程之间可以实现共享,而进程则不行。
3. 什么是互斥锁
如上所诉,线程之间可以相互通信(共享全局变量),那么在执行多线程任务的时候,由于大家都可以共享和修改全局变量,就容易照成数据混乱,互斥锁就是解决这一问题而存在的。
例子:排队上卫生间,进去的人锁门,其他人排队等候。yue~
以电影院买票为例子:本场电影100张票 10个用户同时抢购电影票,每售出一张,显示剩余电影票。
程序如下:
from threading import Thread,Lock #导入线程和互斥锁模块
import time
ticket_num = 100 #初始化100张电影票
mutex = Lock() #实例化一个互斥锁
#规定子线程的执行方式(此处模拟买电影票的过程)
def buy_ticket():
global ticket_num #声明ticket_num为全局变量
mutex.acquire() #锁定
temp = ticket_num
time.sleep(0.2)
ticket_num = temp - 1
print("购票成功,剩余%s张电影票。"%(ticket_num))
mutex.release() #释放锁
if __name__ == '__main__':
list1 = [] #初始化一个列表,用于存放各个子线程 理解:“比如一个人买票的过程为一个子线程,那么n个人买票就是n个子线程,将这n个子线程存于列表中”
for i in range(10): #10个人买票
t = Thread(target=buy_ticket) #实例化子线程 (其实就是将买电影票过程实例化)
list1.append(t) #将每一个人买票这个实例化对象存入列表中
t.start() #启动线程
for i in list1:
t.join() #等待线程结束
假如没有互斥锁,即:
ticket_num = 100 #初始化100张电影票
#mutex = Lock() #实例化一个互斥锁
#规定子线程的执行方式(此处模拟买电影票的过程)
def buy_ticket():
global ticket_num #声明ticket_num为全局变量
#mutex.acquire() #锁定
temp = ticket_num
time.sleep(0.2)
ticket_num = temp - 1
print("购票成功,剩余%s张电影票。"%(ticket_num))
#mutex.release() #释放锁
那么全局变量ticket_num 在初始化时(ticket_num =100时),就可以被存入列表List1中的10个所有子线程同时使用,那么打印的剩下的电影票数就都是“剩下99张”,明明出了10张,却剩下99,显然不对,这其实就是犯了10个人同时上1个卫生间的错误.....
再来理解一下互斥锁:
通过mutex.acquire()锁定,即子线程1在调用全局变量ticket_num时,可以有序进行。