多任务
有很多的场景中的事情是同时进⾏的,⽐如开⻋的时候 ⼿和脚共同来驾驶汽 ⻋,再⽐如唱歌跳舞也是同时进⾏的
多任务的理解
- 并⾏:真的多任务 cpu⼤于当前执⾏的任务
- 并发:假的多任务 cpu⼩于当前执⾏的任务
# -*- coding: UTF-8 -*-
'''
@Project :网络爬虫
@File :1-模拟多任务.py
@IDE :PyCharm
@Author :慕逸
@Date :03/11/2024 10:49
'''
import time
import threading
def sing():
# 子线程
for i in range(3):
print("正在唱歌...%d"%i)
time.sleep(1)
def dance():
# 子线程
for i in range(3):
print("正在跳舞...%d"%i)
time.sleep(1)
if __name__ == '__main__':
print("---开始---")
# 创建线程对象
sing_thread = threading.Thread(target=sing)
dance_thread = threading.Thread(target=dance)
# 启动线程
sing_thread.start() # 启动线程 主线程 会等待子线程执行结束 主线程 再结束
dance_thread.start()
# 等待线程执行完毕
sing_thread.join()
dance_thread.join()
print("---结束---")
查看线程数量
for index,i in enumerate(threading.enumerate()):
print(index,i)
验证子线程的执行与创建
当调⽤Thread的时候,不会创建线程。
当调⽤Thread创建出来的实例对象的start⽅法的时候,才会创建线程以及开始 运⾏这个线程。
# -*- coding: UTF-8 -*-
'''
@Project :网络爬虫
@File :2-验证子线程的执行与创建.py
@IDE :PyCharm
@Author :慕逸
@Date :04/11/2024 09:14
'''
import threading
import time
def demo():
for i in range(5):
print("demo-- %d" % i)
def main():
print(threading.enumerate())
t1 = threading.Thread(target=demo,name="demo")
print(threading.enumerate())
t1.start()
print(threading.enumerate())
if __name__ == '__main__':
main()
继承Thread类创建线程
# -*- coding: UTF-8 -*-
'''
@Project :网络爬虫
@File :3-继承Thread类创建线程.py
@IDE :PyCharm
@Author :慕逸
@Date :04/11/2024 09:29
'''
import threading
import time
class Main(threading.Thread):
def __init__(self,name):
super(Main, self).__init__(name=name)
def run(self):
for i in range(5):
print(i)
def test(self):
for i in range(5):
print(i)
if __name__ == '__main__':
m = Main("test")
m.start()
多线程共享全局变量(线程间通信)
修改全局变量⼀定需要加global吗?
num = 100
li = [1,2,3]
print(id(li)) # 2229490143552
def demo():
global num # 声明全局变量
num += 100
li.append(66)
print(num)
print(id(li)) # 2229490143552
li =li + [4]
print(id(li)) # 2229493655744
在⼀个函数中,对全局变量进⾏修改的时候,是否要加global要看是否对全局变 量的指向进⾏了修改,如果修改了指向,那么必须使⽤global,仅仅是修改了指 向的空间中的数据,此时不⽤必须使⽤global
在Python中,global 关键字用于在函数内部声明一个变量是全局变量,而不是局部变量。这意味着你可以在函数内部修改全局作用域中的变量。
'''
线程间通信
'''
import threading
import time
# 线程共享全局变量 进程不共享
num = 100
def demo1():
global num
num += 1
print("demo1-num = %d" % num) # 101
def demo2():
print("demo2-num = %d" % num) # 101
def main():
t1 = threading.Thread(target=demo1)
t2 = threading.Thread(target=demo2)
t1.start()
t2.start()
print("main-num=%d" % num) #101
if __name__ == '__main__':
main()
多线程参数-args
threading.Thread(target=test, args=(num,))
args参数则是一个元组,包含了传递给target函数的位置参数。
这里的args=(num,)是一个单元素的元组,传递给test函数作为其参数。注意,即使只有一个参数,也需要在括号内添加一个逗号来确保它被视为元组。如果不加逗号,(num)会被解释为一个简单的括号表达式,而不是元组。
# -*- coding: UTF-8 -*-
'''
@Project :网络爬虫
@File :5-多线程参数-args.py
@IDE :PyCharm
@Author :慕逸
@Date :06/11/2024 08:25
'''
import threading
import time
num = [11,22]
def demo1(nums):
num.append(nums)
print("demo1-num = %s" % str(num)) #
def demo2():
print("demo2-num = %s" % str(num)) #
def main():
t1 = threading.Thread(target=demo1,args=(33,))
t2 = threading.Thread(target=demo2)
t1.start()
t2.start()
print("main-num = %s" % str(num)) #
if __name__ == '__main__':
main()
共享全局变量资源竞争
⼀个线程写⼊,⼀个线程读取,没问题,如果两个线程都写⼊呢?
当两个或多个线程同时尝试写入同一个全局变量时,可能会遇到资源竞争(race condition)的问题。资源竞争是指多个线程对共享资源进行非同步访问时导致的不确定行为。这种情况下,最终的结果依赖于线程调度的顺序,这通常是不可预测的,并可能导致数据损坏或逻辑错误。
为什么会出现资源竞争?
- 写-写冲突:当两个线程同时试图修改同一全局变量时,如果其中一个线程的写操作在另一个线程的写操作完成之前发生,那么第一个线程所做的更改可能会被第二个线程覆盖,从而丢失第一个线程的更新。
- 一致性问题:如果写入过程不是原子性的(即不能保证一次完成),那么一个线程可能只完成了部分写入,而这时另一个线程开始读取或写入,这会导致数据处于一种不一致的状态。
# -*- coding: UTF-8 -*-
'''
@Project :网络爬虫
@File :6-线程间资源竞争.py
@IDE :PyCharm
@Author :慕逸
@Date :06/11/2024 09:30
'''
import threading
import time
num = 0
def demo1(nums):
global num
for i in range(nums):
num += 1
print("demo1-num = %d" % num)
def demo2(nums):
global num
for i in range(nums):
num += 1
print("demo2-num = %d" % num)
def main():
t1 = threading.Thread(target=demo1,args=(1000000,))
t2 = threading.Thread(target=demo2,args=(1000000,))
t1.start()
t2.start()
time.sleep(3)
print("main-num = %d" % num)
if __name__ == '__main__':
main()
互斥锁
当多个线程⼏乎同时修改某⼀个共享数据的时候,需要进⾏同步控制
某个线程要更改共享数据时,先将其锁定,此时资源的状态为"锁定",其他线程 不能改变,只到该线程释放资源,将资源的状态变成"⾮锁定",其他的线程才能 再次锁定该资源。互斥锁保证了每次只有⼀个线程进⾏写⼊操作,从⽽保证了 多线程情况下数据的正确性。
创建锁
mutex = threading.Lock()
锁定
mutex.acquire()
解锁
mutex.release()
# -*- coding: UTF-8 -*-
'''
@Project :网络爬虫
@File :6-线程间资源竞争.py
@IDE :PyCharm
@Author :慕逸
@Date :06/11/2024 09:30
'''
import threading
import time
num = 0
# 创建一个互斥锁
mutex = threading.Lock()
def demo1(nums):
global num
# 上锁
mutex.acquire()
for i in range(nums):
num += 1
# 释放锁
mutex.release()
print("demo1-num = %d" % num)
def demo2(nums):
global num
# 上锁
mutex.acquire()
for i in range(nums):
num += 1
# 释放锁
mutex.release()
print("demo2-num = %d" % num)
def main():
t1 = threading.Thread(target=demo1,args=(1000000,))
t2 = threading.Thread(target=demo2,args=(1000000,))
t1.start()
t2.start()
time.sleep(3)
print("main-num = %d" % num)
if __name__ == '__main__':
main()
# 可重入的锁
mutex = threading.Rlock()
死锁
在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时等 待对⽅的资源,就会造成死锁。
# -*- coding: UTF-8 -*-
'''
@Project :网络爬虫
@File :7-死锁.py
@IDE :PyCharm
@Author :慕逸
@Date :06/11/2024 09:52
'''
import threading
import time
class MyThread1(threading.Thread):
def run(self):
# 对mutexA上锁
mutexA.acquire()
# mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁
print(self.name + '----do1---up----')
time.sleep(1)
# 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了
mutexB.acquire()
print(self.name + '----do1---down----')
mutexB.release()
# 对mutexA解锁
mutexA.release()
class MyThread2(threading.Thread):
def run(self):
# 对mutexB上锁
mutexB.acquire()
# mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁
print(self.name + '----do2---up----')
time.sleep(1)
# 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了
mutexA.acquire()
print(self.name + '----do2---down----')
mutexA.release()
# 对mutexB解锁
mutexB.release()
mutexA = threading.Lock()
mutexB = threading.Lock()
if __name__ == '__main__':
t1 = MyThread1()
t2 = MyThread2()
t1.start()
t2.start()
避免死锁
- 程序设计时要尽量避免
- 添加超时时间等
Queue线程
在线程中,访问⼀些全局变量,加锁是⼀个经常的过程。如果你是想把⼀些数据存储到某个队列中,那么Python内置了⼀个线程安全的模块叫做queue模块。Python中的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列Queue,LIFO(后⼊先出)队列LifoQueue。这些队列都实现了锁原语(可以理解为原⼦操作,即要么不做,要么都做完),能够在多线程中直接使⽤。可以使⽤队列来实现线程间的同步。
初始化Queue(maxsize):创建⼀个先进先出的队列。
qsize():返回队列的⼤⼩。
empty():判断队列是否为空。
full():判断队列是否满了。
get():从队列中取最后⼀个数据。
put():将⼀个数据放到队列中。
from queue import Queue
import time
q = Queue(3)
print(q.empty())
print(q.full())
q.put(1)
q.put(2)
q.put(3)
# q.put(4,timeout=3) # 阻塞
# q.put_nowait(5) # 不等待 直接抛出异常
# print("="*30)
# print(q.empty())
# print(q.full())
print(q.get())
print(q.get())
print(q.get())
print(q.get(timeout=3)) # 阻塞
def set_value(q):
index = 0
while True:
q.put(index)
index += 1
print('qsize: '+q.qsize())
time.sleep(1)
def get_value(q):
while True:
print(q.get())
def main():
q = Queue(4)
t1 = threading.Thread(target=set_value,args=(q,))
t2 = threading.Thread(target=get_value,args=(q,))
t1.start()
t2.start()
if __name__ == '__main__':
main()
线程同步
# -*- coding: UTF-8 -*-
'''
@Project :网络爬虫
@File :9-线程同步.py
@IDE :PyCharm
@Author :慕逸
@Date :06/11/2024 10:30
'''
import threading
class XiaoAi(threading.Thread):
def __init__(self,lock):
super().__init__(name='小爱')
self.lock = lock
def run(self):
self.lock.acquire()
print('{}: 在'.format(self.name))
self.lock.release()
self.lock.acquire()
print('{}: 你猜猜现在几点了?'.format(self.name))
self.lock.release()
class TianMao(threading.Thread):
def __init__(self,lock):
super().__init__(name='天猫')
self.lock = lock
def run(self):
self.lock.acquire()
print('{}: 小爱同学'.format(self.name))
self.lock.release()
self.lock.acquire()
print('{}: 现在几点了?'.format(self.name))
self.lock.release()
if __name__ == '__main__':
mutex = threading.Lock()
xiaoai = XiaoAi(mutex)
tianmao = TianMao(mutex)
xiaoai.start()
tianmao.start()
# -*- coding: UTF-8 -*-
'''
@Project :网络爬虫
@File :9-线程同步.py
@IDE :PyCharm
@Author :慕逸
@Date :06/11/2024 10:30
'''
import threading
from threading import Condition
class XiaoAi(threading.Thread):
def __init__(self, cond):
super().__init__(name='小爱')
self.cond = cond
def run(self):
self.cond.acquire()
self.cond.wait()
print('{}: 在'.format(self.name))
self.cond.notify()
self.cond.wait()
print('{}: 你猜猜现在几点了?'.format(self.name))
self.cond.notify()
self.cond.release()
class TianMao(threading.Thread):
def __init__(self, cond):
super().__init__(name='天猫')
self.cond = cond
def run(self):
self.cond.acquire()
print('{}: 小爱同学'.format(self.name))
self.cond.notify()
self.cond.wait()
print('{}: 现在几点了?'.format(self.name))
self.cond.notify()
self.cond.release()
if __name__ == '__main__':
cond = Condition()
xiaoai = XiaoAi(cond)
tianmao = TianMao(cond)
xiaoai.start()
tianmao.start()
另一种方法
with self.cond:
self.cond.wait()
print('{}: 在'.format(self.name))
self.cond.notify()
self.cond.wait()
print('{}: 你猜猜现在几点了?'.format(self.name))
self.cond.notify()