线程的概念
进程中一个分支逻辑的执行过程,一个进程中可以同时有多个分支,将这些分支称之为线程
所有的逻辑都需要在线程上运行
进程的概念
运行在操作系统上的程序就称之为进程
线程 ----> 进程 -----> 操作系统上
线程的目的:
多任务同时执行,且互不干扰,加快操作效率
如果没有多任务同时操作,则可能会出现:
例如:去火锅店吃火锅,问服务员什么时候可以点餐,服务员回答,不好意思,我正在服务另一桌,不能再服务你们
实际上,服务员本身并没有其他任务,只是单纯的等待那一桌吃完,此时其他可以帮助我们点餐,由于设计为单线程,因此无法同时工作
因此一般在一个程序中,会使用多线程来同时完成多个任务,以加快效率
线程的语法:
语法一:使用系统的线程类
# 语法 : threading.Thread(target, args)
# target : 表示线程的目标逻辑,这个需要传入一个函数名称
# 表示当前线程执行什么逻辑代码
# args : 表示参数列表,之前设置的函数在执行过程中需要传入的参数
# 注意:这语法会返回创建出来的线程对象,需要将这个线程对象存储并使用
# 例如:
import threading
def dance():
while True:
print("正在唱歌......")
def sing():
while True:
print("-------正在跳舞")
if __name__ == "__main__":
# 利用系统的线程的模块
t1 = threading.Thread(target=dance)
t2 = threading.Thread(target=sing)
# 开启线程
t1.start()
t2.start()
现象:
解释: 为什么没有同时运行(运行有次数的差异)
原因:CPU分配时间片时是随机的,因此每条线程获取时间片的次数及顺序是无规律的
线程的创建方法二:创建自己的线程类.
# 第一步:引入线程的模块
import threading
# 第二步:创建一个类别继承至系统的线程类
class MyThread(threading.Thread):
def __init__(self, name): # 编写构造函数
super().__init__(name=name) # 调用父类的构造函数给线程命名
# 第三步:重写run函数(此函数为线程的主逻辑函数)
def run(self):
while True:
print("{}正在跳舞......".format(self.name))
class Sing(threading.Thread):
def __init__(self, name): # 编写构造函数
super().__init__(name=name) # 调用父类的构造函数给线程命名
# 第三步:重写run函数(此函数为线程的主逻辑函数)
def run(self):
while True:
print("{}正在唱歌".format(self.name).rjust(20, "-"))
if __name__ == '__main__':
t1 = MyThread('张三')
t2 = Sing('李四')
t1.start()
t2.start()
练习:
有一个车站,一共有100张车票,现在有四个窗口同时售票,
每次出售后,输出显示: XXX窗口出售了一张票,当前还剩下X张票
当票出售完毕后,程序结束
# 有一个车站,一共有100张票,分别有四个窗口同时进行售票
# 每次售票成功后,都输出显示信息: XXX出售了一张票,还剩下X张票
# 如果票出售完毕则结束
import threading
class Station:
def __init__(self):
# 创建一个变量来表示总票数
self.ticket = 100
# 编写一个函数用于出售车票
def sell(self):
if self.ticket > 0:
self.ticket -= 1
return True
else:
return False
class Seller(threading.Thread):
def __init__(self, station, name):
super().__init__(name=name)
# 编写一个变量用于记录当前售票员在哪一个车站售票
self.station = station
def run(self):
# 开启循环售票
while True:
result = self.station.sell()
if result:
print("{}成功出售了一张票,当前还剩下{}张票".format(self.name, self.station.ticket))
else:
print("已经没有票了")
break
if __name__ == '__main__':
# 创建车站对象
station = Station()
# 创建售票员(售票窗口)
seller1 = Seller(station, '张三')
seller2 = Seller(station, '李四')
seller3 = Seller(station, '王五')
seller4 = Seller(station, '赵六')
# 开启售票
seller1.start()
seller2.start()
seller3.start()
seller4.start()
结果:由于CPU分配时间片时是随机分配,因此结果会出现小部分混乱
李四成功出售了一张票,当前还剩下15张票
王五成功出售了一张票,当前还剩下14张票
王五成功出售了一张票,当前还剩下13张票
赵六成功出售了一张票,当前还剩下12张票
赵六成功出售了一张票,当前还剩下11张票李四成功出售了一张票,当前还剩下9张票
李四成功出售了一张票,当前还剩下8张票
张三成功出售了一张票,当前还剩下10张票李四成功出售了一张票,当前还剩下7张票
张三成功出售了一张票,当前还剩下6张票
张三成功出售了一张票,当前还剩下5张票
王五成功出售了一张票,当前还剩下4张票李四成功出售了一张票,当前还剩下3张票
李四成功出售了一张票,当前还剩下2张票张三成功出售了一张票,当前还剩下1张票
王五成功出售了一张票,当前还剩下0张票
已经没有票了
已经没有票了
多线程共同访问一个变量的问题(共享数据问题)
当一个数据被多个线程共享时,由于线程的时间片原理,可能会导致数据的混乱(同时访问),此时的数据将不再准确
此时,就需要进行数据保护,Python提供了一个互斥锁的机制
类: threading.Lock
机制: 在锁范围的内的数据仅允许一条线程访问,当数据被访问时,其他线程在范围外等待,用此方式来保证数据的准确
语法:
例如:
import threading
number = 0
def func1():
# 声明使用外部数据number(第三行的数据)
global number
for i in range(1000000): # 循环一百万次
number += 1 # number的数据加一百万个1
print("函数一的计算结果为:", number)
def func2():
# 声明使用外部数据number(第三行的数据)
global number
for i in range(1000000): # 循环一百万次
number += 1 # number的数据加一百万个1, number = number + 1
print("函数二的计算结果为:", number)
if __name__ == '__main__':
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t1.start()
t2.start()
预计结果:最后的数据应该是二百万,但是由于线程机制,结果是不固定,但大部分情况不够二百万
函数一的计算结果为: 1271354
函数二的计算结果为: 1374950
可以将数据操作设置一个互斥锁,使用互斥锁来进行数据的保护
操作分为三步:
# 第一步:获取锁对象 lock = threading.Lock()
# 第二步:开启互斥锁 lock.acquire() # 获取互斥锁(开启互斥效果)
# 第三步:释放互斥锁 lock.release() # 释放互斥锁(释放后,其他线程将可以访问这个数据)
例如:
def func1():
# 声明使用外部数据number(第三行的数据)
global number
for i in range(1000000): # 循环一百万次
# 第二步:开启互斥锁
lock.acquire() # 获取互斥锁(开启互斥效果)
number += 1 # number的数据加一百万个1
# 第三步:释放互斥锁
lock.release() # 释放互斥锁(释放后,其他线程将可以访问这个数据)
print("函数一的计算结果为:", number)
结果为:
函数一的计算结果为: 1870502
函数二的计算结果为: 2000000
此时:最后的数据确实达到了预期效果,但是运行时间比之前更长(运行中出现了访问数据时等待时间),为了数据准确还是会加上互斥锁
全部代码:
import threading
number = 0
# 第一步:获取锁对象
lock = threading.Lock()
def func1():
# 声明使用外部数据number(第三行的数据)
global number
for i in range(1000000): # 循环一百万次
# 第二步:开启互斥锁
lock.acquire() # 获取互斥锁(开启互斥效果)
number += 1 # number的数据加一百万个1
# 第三步:释放互斥锁
lock.release() # 释放互斥锁(释放后,其他线程将可以访问这个数据)
print("函数一的计算结果为:", number)
def func2():
# 声明使用外部数据number(第三行的数据)
global number
for i in range(1000000): # 循环一百万次
# 第二步:开启互斥锁
lock.acquire() # 获取互斥锁(开启互斥效果)
number += 1 # number的数据加一百万个1, number = number + 1
# 第三步:释放互斥锁
lock.release() # 释放互斥锁(释放后,其他线程将可以访问这个数据)
print("函数二的计算结果为:", number)
if __name__ == '__main__':
t1 = threading.Thread(target=func1)
t2 = threading.Thread(target=func2)
t1.start()
t2.start()