day34
一丶线程的理论知识
什么是线程:
1.线程是一堆指令,是操作系统调度的最小单位
2.线程具有执行能力
3.线程依赖于进程
4.具有主从关系(人为定义,每一个进程都至少有一个主线程
二丶开启线程的两种方式(Thread)
类的方式开启线程
### 利用到Thread
from threading import Thread
class MyThread(Thread):
def run(self) -> None: # 必须重写run函数
print(f'{self.name} 被开启了~~')
if __name__ == '__main__':
t=MyThread() # 实例化一个自定义线程类对象
t.start() # 启动子线程
print('in 主线程')
函数的方式开启线程
### 函数开启线程
from threading import Thread
def task(name):
print(f'在子线程中: {name}')
if __name__ == '__main__':
t=Thread(target=task,args=('abc',)) # 实例化线程对象
t.start() # 启动子线程
print('主~~~~')
三丶线程和进程之间的对比
进程VS线程:
1.线程的启动速度 快于 进程的启动速度
2.线程之间数据可以共享,进程之间不能数据共享(必须依靠队列才能实现)
3.线程开销小,进程开销大
4.在运行速度上,进程和线程是没有可比性.(两个不同的概念,线程具有执行能力,进程不具有执行能力)
四丶线程的其他方法
两种:
1.线程对象方法
2.threading对象方法
# -*-coding:utf-8-*-
# Author:Ds
from threading import Thread
import threading
import time
### 定义开启子线的方法
def task():
time.sleep(1) # 睡1秒, 保证所有的子线程都能存活1秒 以上
print(f'123')
print(threading.current_thread().name) #子线程对象名字
if __name__ == '__main__':
for i in range(6):
t=Thread(target=task,name='firstIn') #target指定子线程任务,name子线程名
t.start()
### 1. 线程对象方法
print(t.isAlive()) # 判断线程是否还存活
t.setName('32141') # 设置name属性
print(t.getName()) # 获得线程名
### 2 threading模块方法
print(threading.current_thread()) #线程对象
print(threading.enumerate()) # 列表 [<_MainThread(MainThread, started 10748)>, <Thread(firstIn, started 3696)>,]
print(threading.active_count()) # 获取活跃的线程数量 (包括主线程 ) 7个
print('主线程')
五丶守护进程
什么是守护线程:
1.守护线程必须等待所有的非守护线程以及主线程结束之后才结束
2.本质还是子线程,在开启前被设置成守护线程.
### 守护线程
from threading import Thread
import time
def task(name):
print(f'{name} is running')
time.sleep(1) # 当主线程结束了,当前守护线程也就结束了
print(f'{name} is over')
if __name__ == '__main__':
t = Thread(target=task,args=('abc',))
t.daemon = True # 将一个子线程设置为守护线程
t.start()
print('主线程')
容易产生歧义:
1.首先 要明确, 在同一时刻, CPU只允许一个任务的存在, 遇到IO阻塞进行任务切换
2.守护进程VS守护线程
守护线程:必须等待所有的非守护线程执行完毕以及主线程执行完毕,才能结束
守护进程:守护进程不会等待所有的非守护进程完毕才结束.只要主进程GG,守护进程GG
### 守护线程:
from threading import Thread
import time
def foo():
print(123)
time.sleep(1) # sleep 表示要切换, 执行bar子线程
print("end123")
def bar():
print(456) # 执行 守护线程必须等待非守护线程执行完毕才能结束
time.sleep(3)
print("end456")
if __name__ == '__main__':
t1=Thread(target=foo)
t2=Thread(target=bar)
t1.daemon = True
t1.start()
t2.start()
print("main-------")
六丶互斥锁
含义:
1.互斥锁,同步锁,锁.都是同一种锁LOCK
2.在并发时,保证数据的安全('串行')
###互斥锁 实现 '并发'
# -*-coding:utf-8-*-
# Author:Ds
from threading import Thread
from threading import Lock # 这是线程的 互斥锁
import time
x=100
def task(lock):
lock.acquire() # 加锁 实现 '并发' ,保证数据的安全性
global x
temp = x
time.sleep(0.1)
temp -= 1
x = temp
print(x)
lock.release()
if __name__ == '__main__':
lock=Lock() # 实例化锁对象
#### 如果想要实现并发. 1.把所有的实例对象都添加列表中,2 join循环列表, 每一个线程都必须等待上一个线程执行完,必须等待上一个线程执行完
t_li=[] # 存放线程对象, 实现并发
for i in range(100): # 创建100个子线程
t=Thread(target=task,args=(lock,))
t_li.append(t)
t.start()
for el in t_li:
el.join() # 循环列表, 每一个线程都必须等待上一个线程执行完,必须等待上一个线程执行完
print(f'最后x:{x}')
七丶死锁现象,递归锁
死锁含义(Lock):
一个资源被多次调用,多次调用资源未能释放,会造成一种互相等待的现象.在没有外力作用下,只能停留在这,此时的系统处于锁死状态.
死锁现象(2种):
1.当一个进程或者一个线程一直占调用或者占用同一把锁Lock时,而不释放资源会导致其他进程/线程无法获得锁,就会出现锁死现象.一直出去阻塞acquire()状态
代码见:
### 当一个锁对象已经被上锁, 试图再次加锁, 就会造成锁死.
from multiprocessing import Lock
def task1(loc):
print('task1')
loc.acquire()
print('task1: 开始打印')
time.sleep(random.randint(1,3))
print('task1: 结束打印')
loc.release()
def task2(loc):
print('task2')
loc.acquire() # 第一层锁
loc.acquire() #第二层锁, 试图再次加锁,由于锁对象已经被占用(已经锁上了,还没有释放)再次上锁,就会造成锁死 (程序被卡主)~~~
loc.release()
print('task2: 开始打印')
time.sleep(random.randint(1,3))
print('task2: 结束打印')
loc.release()
def task3(loc):
print('task3')
loc.acquire()
print('task3: 开始打印')
time.sleep(random.randint(1,3))
print('task3: 结束打印')
loc.release()
if __name__ == '__main__':
loc=Lock()
p1=Process(target=task1,args=(loc,)).start()
p2=Process(target=task2,args=(loc,)).start()
p3=Process(target=task3,args=(loc,)).start()
2.当有两个进程/线程同时想要获取两个锁时,由于两者都是处于竞争关系.就可能会出现两者都阻塞在同一放,都无法同时获得两个锁 或者 要获取对方已经获得的还没有释放的锁. 代码如下
# -*-coding:utf-8-*-
# Author:Ds
from threading import Thread
from threading import Lock
import time
# 两把 不一样的锁
lock_A=Lock()
lock_B=Lock()
class MyThread(Thread):
def run(self) -> None:
self.f1()
self.f2()
def f1(self):
lock_A.acquire()
print(f'{self.name} 拿到A锁')
lock_B.acquire() #
print(f'{self.name} 那到B锁')
lock_B.release()
lock_A.release()
def f2(self):
lock_B.acquire()
print(f'{self.name} 拿到B锁')
time.sleep(1)
lock_A.acquire()
print(f'{self.name} 拿到A锁')
lock_A.release()
lock_B.release()
if __name__ == '__main__':
for i in range(2): # 实例化两个线程对象
t=MyThread() # 简单命名,线程1和线程2
t.start()
print('in 主线程')
### 口述上述代码的过程:
#1. 线程 1 执行f1函数, 拿到A锁, 线程2 也要拿A锁. 由于线程1已经拿到A锁,线程2必须等待A锁
#2. 线程1 继续执行f1函数 ,此时 B锁没有人使用. 轻松的执行完 f1函数
#3. 重点: 线程1执行f2函数 拿走B锁. 而此时A锁已经被释放了,现在线程2拿走A锁,
#4. 重点: 线程1执行f2函数 已经拿走B锁,继续执行需要A锁. 此时发现A锁已经被线程2拿走了, 线程2 执行f1函数,已经拿走了A锁,需要B锁.而此时B锁在线程1中.
### 由于竞争关系的存在. 两个线程对同一个资源进行抢夺. 一直卡在acquire() , 造成死锁.
递归锁(RLock):
1.防止线程锁死
2.引用计数原则:非0不能被抢,引用1次锁一次,引用N次锁N次.
3.在并发时,保证数据的安全('串行')
# -*-coding:utf-8-*- # Author:Ds ### 递归锁是一把锁,锁上有记录,只要acquire一次,锁上就计数1次, acquire2次,锁上就计数2次, # release1次,减一, # 只要递归锁计数不为0,其他线程不能抢. from threading import Thread from threading import RLock import time # 同一个锁对象,两把一样的锁 ,内部采用单例模式 lock_A=lock_B=RLock() class MyThread(Thread): def run(self) -> None: self.f1() self.f2() def f1(self): lock_A.acquire() print(f'{self.name} 拿到A锁') lock_B.acquire() # print(f'{self.name} 那到B锁') lock_B.release() lock_A.release() def f2(self): lock_B.acquire() print(f'{self.name} 拿到B锁') time.sleep(1) lock_A.acquire() print(f'{self.name} 拿到A锁') lock_A.release() lock_B.release() if __name__ == '__main__': for i in range(2): # 实例化两个线程对象 t=MyThread() t.start() print('in 主线程')
八丶信号量
含义:
信号量允许多个线程或者进程同时进入
描述:
描述:一个网吧,有三台电脑,一开始三台电脑都没有人.这时候来了5个人要上网,网管允许3个人进入网吧,使用电脑. 剩下的2个人就必须在门外等待,此后来的认也要在门外等待. 如果这时候有1个人已经上完网了,网管得知后,打开网吧的门,让后面的1个人进入网吧上网.如果这时候又有2个人已经上网完毕离开了,网管又打开门,让后面的2个人进入网吧上网.
在网吧系统中,上网电脑: 属于公共资源,上网的人属于一个线程, 网管就是起着信号量的作用
# -*-coding:utf-8-*- # Author:Ds ### 信号量: 同一时间,只允许指定数量的线程工作.(多余的线程排队等候) ### 信号量允许多个线程或者进程同时进入 from threading import Thread from threading import current_thread from threading import Semaphore import time import random sm=Semaphore(3) # 定义信号量对象 同一时刻只允许3个任务被处理 def Safe_Internet(): sm.acquire() print(f'{current_thread().name} 正在上网') time.sleep(random.randint(1,3)) # 增加随机性 sm.release() print(f'\033[0;35m {current_thread().name} 已经上完网了 \033[0m') if __name__ == '__main__': t_l=[] for i in range(20): t=Thread(target=Safe_Internet,) t_l.append(t) t.start() for i in t_l: i.join() print('in 主线程~~~~')