并发编程之⏸多线程
1、线程相关理论知识
2、开启线程的方式
3、线程对象的相关方法
4、守护线程
5、线程互斥锁
6、信号量
7、死锁与递归
8、事件Event
9、定时器⏰
10、线程queue
1、📝📝📝线程相关知识点📝📝📝
一、什么是线程❓❓❓
线程:一个流水线运行的过程
线程是一个执行单位,cpu执行的就是线程
进程是一个资源单位
公司的运行,部门是资源单位,任务的运行的实质是其中的人在运行
二、线程⚔⚔⚔⚔⚔⚔进程
1. 线程共享创建它的进程的地址空间;进程有自己的地址空间
2. 线程可以直接访问其进程的数据段;进程有自己的父进程的数据段副本。
3.线程可以直接与其进程中的其他线程通信;进程必须使用进程间通信来与同级进程通信。
4. 新线程很容易创建;新的进程需要父进程的复制。
5. 线程可以对同一进程的线程进行相当大的控制;进程只能对子进程进行控制。
6. 主线程的改变(取消,优先级的改变,等等)可能会影响进程中其他线程的行为;父进程的更改不会影响子进程。
2、🐱🐉🐱🐉🐱🐉🐱🐉🐱🐉🐱🐉开启线程的方式🤺
🐱👤方式一
import os
from threading import Thread,current_thread
def task(name):
print('%s is running' %current_thread().name)
if __name__ == '__main__':
t = Thread(traget=task,args=('线程1',))
t.start()
print('主线程',current_thread().name)
#ps: 太快了,所以会出现屏幕打印在同一行的情况。莫慌,这是正常的🐳
比如----- Thread-1 is running主线程 MainThread
🐱👓方式二
from threading import Thread
class Mythread(Thread):
def __init__(self,n):
super().__init__()
self.n=n
def run(self):
print('%s is running' %self.name)
if __name__ == '__main__':
t = Mythread('线程1')
t.start()
🔈🔈🔈根据打印结果可以看的出 线程的开销是远远小于进程的,开启的速度非常快
🐱🚀线程的内存空间共享
n = 100
def task():
global n
n = 0
if __name__ == '__main__':
t = Thread(target=task)
t.start()
t.join() # 等待结束是为了防止还没有运行task就打印了主线程哦
print('主线程',n)
#ps:打印结果为0
🔈🔈🔈所以,同一进程下的多个线程 共享该进程的内存资源
3、📜📜📜线程对象的相关方法📜📜📜
from threading import Tread,current_thread,active_count
def task():
print('%s is running'%current_thread().name)
if __name__ == '__main__':
t = Thread(target=task)
t.start()
print(active_count()) # 查看子线程个数
print('主线程')
print(t.is_alive()) # 判断线程是否存活
print('主线程',current_thread().name)
4、🛡⚔⚔🛡守护线程🛡⚔⚔🛡
一、对于⏰主进程⏰来说,守护进程随着主进程的代码运行完毕而结束,主进程代码运行完毕,但是主进程并未终止运行,会等着非守护的子进程结束
二、对于⏰主线程⏰来说,守护进程等着主进程运行完毕才结束,是指非守护的所有子线程运行结束
三、守护线程🍭小例子🍭
import time
from threading import Thread,current_thread
def task(n):
print('%s is running' %current_thread().name)
time.sleep(n)
print('%s is ending' %current_thread().name)
if __name__ == '__main__':
t1 = Thread(target=task,args=(3,))
t2 = Thread(target=task,args=(5,))
t3 = Thread(target=task, args=(100,)) # 没有运行完毕 随着主进程结束而结束,看不到Thread-3 is ending
t3.daemon=True
t1.start()
t2.start()
t3.start()
print('主')
5、🔐🔐🔐互斥锁🔐🔐🔐(线程)
锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据
测试代码
improt time
from threading import Thread,Lock
n = 100
mutex = Lock()
def task():
global n
with mutex:
temp = n
time.sleep(0.01)
n = temp-1 🔈🔈🔈#ps: 线程太快了。如果不加🔒 在测试过程中也会看到
if __name__ == '__main__':
thread_1 = []
for i in range (100):
t = Thread(targat=task)
thread_1.append(t)
t.start()
for obj in thread_1:
obj,join()
print('主线程',n)
开启子线程太快了。如果不加锁🔒 测试的结果也是0,看起来数据安全没有问题,但是用timesleep睡个一秒就会发现修改的问题。
6、📡📡📡信号量📡📡📡
同进程的一样、Semaphore管理一个内置的计数器
每当调用acquire()时内置计数器-1
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。
实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):
import random
def func():
sm.acquire():
print('%s get sm' %threading.current_thread().getName())
time.sleep(random.randint(1,2))
sm.release()
id __name__ == '__main__':
sm = Semaphore(5) #限制信号量为5
for i in range(23):
t = Thread(target=func)
t.start()
7、☠死锁🔒与递归👣👣
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁
from threading import Thread,Lock
import time
mutexA = Lock()
mutexB = Lock()
class Mythread(Thread):
def __init__(self,name):
super().__init__()
self.name = name
def run(self) -> None:
self.f1()
self.f2()
def f1(self):
mutexA.acquire() #线程1 抢到了🔒A
print("%s 抢到了A锁" %self.name)
mutexB.acquire()
print("%s 抢到了B锁" %self.name) # 线程1 抢到了🔒B
mutexB.release() # 线程1 释放🔒B
mutexA.release() # 线程1 释放🔒A
def f2(self):
mutexB.acquire()
print("%s 抢到了B锁" % self.name) # 线程1 抢到了🔒B
time.sleep(0.1) #🔒A被释放,所以线程2 抢到🔒A
mutexA.acquire()
print("%s 抢到了A锁" % self.name) # 线程1 继续抢🔒A 但是 🔒A已经被线程A抢了,进入阻塞状态
mutexA.release()
mutexB.release()
if __name__ == '__main__':
t1 = Mythread("线程1")
t2 = Mythread("线程2")
t3 = Mythread("线程3")
t4 = Mythread("线程4")
t1.start()
t2.start()
t3.start()
t4.start()
print("主线程")
解决方法,递归锁,在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁🔏RLock。
这个🔏RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
mutexA=mutexB=threading.RLock() #一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止
from threading import Thread,RLock,Lock
import time
mutexA = mutexB = RLock()
class Mythread(Thread):
def __init__(self,name):
super().__init__()
self.name = name
def run(self) -> None:
self.f1()
self.f2()
def f1(self):
mutexA.acquire()
print("%s 抢到了A锁" %self.name)
mutexB.acquire()
print("%s 抢到了B锁" %self.name)
mutexB.release()
mutexA.release()
def f2(self):
mutexB.acquire()
print("%s 抢到了B锁" % self.name)
time.sleep(0.1)
mutexA.acquire()
print("%s 抢到了A锁" % self.name)
mutexA.release()
mutexB.release()
if __name__ == '__main__':
t1 = Mythread("线程1")
t2 = Mythread("线程2")
t3 = Mythread("线程3")
t4 = Mythread("线程4")
t1.start()
t2.start()
t3.start()
t4.start()
print("主线程")
8、🌪🌪🌪事件Event🌪🌪🌪
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。
🚦🚦🚦红绿灯小案例(真的只有红灯和绿灯)
from threading import Event,Thread,current_thread
import time
import random
e = Event()
def task():
while True:
e.clear()
print('红灯亮')
time.sleep(2)
e.set()
print('绿灯亮')
time.sleep(2)
def task2():
while True:
if e.is_set():
print('绿灯亮了,快跑啊%s' %current_thread().name)
break
else:
print('stop!站住别动%s' %current_thread().name)
e.wait()
if __name__ == '__main__':
Thread(target=task1).start()
while True:
time.sleep(random.randint(1,3))
Thread(target=task2).start()
9、⌛⌛⌛定时器⏳⏳⏳
一句话:指定时间后操作
from threading import Timer
def hello():
print("hello, world")
t = Timer(1, hello)
t.start() # after 1 seconds, "hello, world" will be printed
# 其他功能
q = queue.PriorityQueue(3)
q.put((10,111)) # 前面优先级别
q = queue.lifoQueue(3) # 倒着取
10、线程queue
queue队列 :使用import queue,用法与进程Queue一样
queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.
当信息必须在多个线程之间安全交换时,队列在线程编程中特别有用。
import queue
队列
q = queue.Queue(3)
q.put(111)
q.put("aaa")
q.put((1,2,3))
print(q.get())
print(q.get())
print(q.get())
堆栈
q = queue.LifoQueue(3)
q.put(111)
q.put("aaa")
q.put((1,2,3))
print(q.get())
print(q.get())
print(q.get())
优先级队列
= queue.PriorityQueue(3)
q.put((10,111))
q.put((11,"aaa"))
q.put((-1,(1,2,3)))
print(q.get())
print(q.get())
print(q.get())