目录
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()方法中。
注:
(1)当用1.1中自定义一个函数(如def lkw)以“规范或者说限制”进程或线程时,其实例化对象为:
t = Process(target=lkw)
(2)通过继承改写父类Process(threading.Thread)方法,由子类直接实例化对象,即:
t = Subxxx()
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("---主进程结束---")
对比如上两个程序可知,对于全局变量,子线程之间可以实现共享,而进程则不行。
2.1 什么是互斥锁
如上所诉,线程之间可以相互通信(共享全局变量),那么在执行多线程任务的时候,由于大家都可以共享和修改全局变量,就容易照成数据混乱,互斥锁就是解决这一问题而存在的。
例子:排队上卫生间,进去的人锁门,其他人排队等候。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时,可以有序进行。
互斥锁优点:
线程间的全局变量可以实现通信,互斥锁则解决了可能存在的“通信混乱”的问题。
2.2 线程间的通信(通过队列)
Multiprocessing.Queue模块可以实现进程间的通信,同样的 线程之间也可以通过Queue实现线程间的通信,不同点是我们需要导入queue标准库下的Queue队列 使用的方法和Multiprocessing.Queue是相同的。
这个线程中使用的Queue,使用的是一个生产消费模式。
生产者:产生数据的模块
消费者:处理数据的模块
缓冲区:生产者和消费者之间的缓冲区是仓库,生产者负责向仓库添加商品,而消费者负责从仓库中取出商品,即:
通过例子演示一下:
from queue import Queue #导入线程队列模块
import random,threading,time
#定义一个继承线程threading.Thread的子类,其实例化对象就是一个子线程(该线程即模拟,抽象生产者类的生产过程)
class Producer(threading.Thread):
#重写threading.Thread的构造方法(应对子线程(生产过程)中的增加的静态属性,比如名字,数据等)
def __init__(self,name,queue):
threading.Thread.__init__(self,name=name) #调用父类构造方法
self.data = queue # 将queue传递给参数self.data
def run(self): #重写方法 (应对子线程(生产过程)中增加的动态属性,比如生产产品的过程【如下将其模拟成将产品加入队列】)
for i in range(5):#生产5件产品
print("生产者%s将产品%d加入到队列中!"%(self.getName(),i)) #self.getName() 表示获取生产者的名字
self.data.put(i) #将数据(生产的产品)写进队列
time.sleep(random.random()) #休眠随机时间
print("生产者%s完成任务!"%(self.getName()))
# 定义一个继承线程threading.Thread的子类,其实例化对象就是一个子线程(该线程即模拟,抽象消费者类的消费过程)
class Custemer(threading.Thread):
# 重写threading.Thread的构造方法(应对子线程(消费过程)中的增加的静态属性,比如名字,数据等)
def __init__(self, name, queue):
threading.Thread.__init__(self, name=name) # 调用父类构造方法
self.data = queue # 将queue传递给参数self.data
def run(self): # 重写方法 (应对子线程(生产过程)中增加的动态属性,比如生产产品的过程【如下将其模拟成将产品加入队列】)
for i in range(5):
val = self.data.get() #从队列中取出数据(模拟消费过程)
print("消费者%s将产品%d从队列中取出!" % (self.getName(), val))
time.sleep(random.random()) # 休眠随机时间
print("消费者%s完成消费!" % (self.getName()))
if __name__ == '__main__':
print("---主线程启动---")
queue = Queue() #实例化线程队列
producer = Producer("香菱",queue) # 实例化子进程 //继承子类带参数的情况,注意哦。xxx(xxx,ooo)
consumer = Custemer("锅巴",queue)
producer.start() #开始进程
consumer.start()
producer.join()
consumer.join()
print("---主线程结束---")
总结:
(1)一个线程child_thread相对于python环境父类threading.Thread有其特殊的静态属性和动态属性,则可以通过定义一个继承threading.Thread的子类来实例化该线程child_thread。在继承的过程中,注意对child_thread的特殊静态属性和动态属性的安排,即方法重写:
class Producer(threading.Thread): #重写threading.Thread的构造方法(应对子线程(生产过程)中的增加的静态属性,比如名字,数据等) def __init__(self,name,queue): threading.Thread.__init__(self,name=name) #调用父类构造方法 self.data = queue # 将queue传递给参数self.data
注意其参数传递方式。
(2) 对于如上:
self.data = queue # 将queue传递给参数self.data
该代码将主程序中定义的队列queue赋值给了self.data,那么由队列存入和取出数据的操作就有如下代码:
self.data.put(i) #将数据(生产的产品)写进队列
val = self.data.get() #从队列中取出数据(模拟消费过程)
(3)由:
def __init__(self,name,queue): threading.Thread.__init__(self,name=name) #调用父类构造方法 self.data = queue # 将queue传递给参数self.data
则通过self.getName()可以读取name参数对应的值 。
3. 线程与进程的区别
(1)进程是系统进行资源分配和调度的一个独立单位,线程是进程的一个实体,是cpu调度和分派的基本单位。
比如QQ,对于任务管理器而言,运行的QQ就是一个进程;
而QQ中的收发信息,播放音乐,查看网页,下载文件等功能就是一个个线程。
(2)进程之间是相互独立的,多进程中同一个变量,各自有一份备份存在每个进程中,但互不影响;而同一个进程的多个线程是内存共享的,所有变量都由所有线程共享。
(3)由于进程间是相互独立的,因此一个进程的崩溃不会影响其他进程;而线程是包含在进程之内的,线程的崩溃就会引发进程的崩溃,继而导致同一进程内的其他线程也崩溃。