day32 python 多线程 初始锁
一.操作系统/应用程序
用户
应用程序: 我们
框架开发人员
应用程序: 解释器 程序员
操作系统: 程序员
硬件
二.操作中的并发,并行,线程,进程
1.并发和并行
并发:程序的并发执行, 多个程序在同一时间间隔运行,看到了一个同时运行的假象,实际同一时刻同一个cpu只能有一个程序被执行
并行:这个才是同时执行
2.单进程,单线程的应用程序
之前写的程序(除了socketserver): 都是单进程,单线程的应用程序
print('666')
3.到底什么是线程,什么是进程.
是操作系统的进程和线程
4.单进程,多线程的应用程序
import threading
def func(arg):
print(arg)
t = threading.Thread(target=func,args=('666',)) #在这里创建了一个子线程,默认存在一个主线程
t.start()
5.程序可创建多个进程,默认一个
进程可创建多个线程,默认一个
6.线程:工作的最小单元, 共享进程中所有的资源, 创建线程的原因: 每个线程可以分担一些任务,最终完成最后的结果
进程:独立开辟内存空间,创建进程的原因: 为了进程之间的数据隔离(可以利用特殊的方式进行通信)
三.其他语言线程,进程
java, c# 崇尚一个进程: 里面多个线程. 很少使用多进程
python:多CPU时:
一个进程,同一时刻只有一个线程能被cpu执行.
创建多个进程,实现和java, c#一样的效果,但是进程是浪费资源的
四.python中线程和进程(GIL锁: 全局解释器锁) (interpreter 解释器)
GIL锁: 多cpu时,限制一个进程,同一时刻,只有一个线程可以被cpu调度
python唯一缺点: 多线程时,无法利用多 cpu 的优势
五.以上部分总结:
1.操作系统帮开发者操作硬件
2.程序员写好代码在操作系统上运行(依赖解释器)
3.任务特别多时:
你写好代码,
交给解释器运行,
解释器读取你的代码,
交给操作系统去执行,
根据你的代码去选择创建多少线程/多少进程去执行
3.1.之前写的程序是串行:
这里的代码: 创建了单进程,里面有个单线程去执行任务
操作系统调用硬件
import requests
import uuid
url_list = ['https://club2.autoimg.cn/album/g2/M00/E4/50/userphotos/2019/08/13/12/500_ChsEml1SPiqAWFwWAAiDwSxrzcg822.jpg',
'https://club2.autoimg.cn/album/g2/M0B/E4/54/userphotos/2019/08/13/12/500_ChsEml1SPkqAC3U-AAgJb16oPbA580.jpg',
'https://club2.autoimg.cn/album/g2/M05/E4/53/userphotos/2019/08/13/12/500_ChsEml1SPkCAGaEcAAhkEHqfWkw467.jpg',
'https://club2.autoimg.cn/album/g25/M01/09/54/userphotos/2019/08/13/12/500_ChsEel1SPk6AXODKAAd2fOmE6kU698.jpg'
]
def task(url):
rst = requests.get(url)
file_name = str(uuid.uuid4()) + '.jpg' #搞个不一样的文件名
with open(file_name, mode='wb') as f: #把下载的内容写入文件
f.write(rst.content)
print(rst.content)
for url in url_list: #有个问题, 线程不能任意开, 得有限制
task(url)
3.2.现在写的程序:(多线程的并发(由于GIL锁, 多线程对cpu利用的限制,这种并发程度要取决于cpu和io处理的占比))
这里的代码: 创建了单进程,里面有 5 个线程去执行任务(1个主线程, 4个子线程)
GIL锁,同一时间,同一进程里面只有一个线程能被cpu执行
问题来了: (那这里的5个线程岂不是不能并发:不是的,因为socket连接不太占cpu资源,而是属于io操作.所以这里虽然有GIL锁,多线程也可以并发执行)
原因在于cpu的分时处理, 不是一个线程按着一直到执行完: cpu虽然不能并发, 但是当多个线程的cpu都执行完, io操作时就不用cpu,也就不被GIL限制了
并发的程度:
取决于cpu和io操作的占比, cpu越少这种多线程并发越好, cpu占的越多这种多线程就相当于串行
操作系统调用硬件
import threading
import requests
import uuid
url_list = ['https://club2.autoimg.cn/album/g2/M00/E4/50/userphotos/2019/08/13/12/500_ChsEml1SPiqAWFwWAAiDwSxrzcg822.jpg',
'https://club2.autoimg.cn/album/g2/M0B/E4/54/userphotos/2019/08/13/12/500_ChsEml1SPkqAC3U-AAgJb16oPbA580.jpg',
'https://club2.autoimg.cn/album/g2/M05/E4/53/userphotos/2019/08/13/12/500_ChsEml1SPkCAGaEcAAhkEHqfWkw467.jpg',
'https://club2.autoimg.cn/album/g25/M01/09/54/userphotos/2019/08/13/12/500_ChsEel1SPk6AXODKAAd2fOmE6kU698.jpg'
]
def task(url):
rst = requests.get(url)
file_name = str(uuid.uuid4()) + '.jpg' #搞个不一样的文件名
with open(file_name, mode='wb') as f: #把下载的内容写入文件
f.write(rst.content)
print(rst.content)
for url in url_list: #有个问题, 线程不能任意开, 得有限制
t = threading.Thread(target=task, args=(url,))
t.start()
4.python 多线程的并发程度,效率问题(GIL锁)
计算密集型: 占用cpu的时间长, 相当于串行, 无并发效率可言
io密集型: 占用cpu的时间不长,并发效率高
java,c# 多线程的并发, 没有GIL锁
计算密集型:高
io密集型:高
5.python 多进程的并发程度(浪费资源,为了让计算密集型效率高,不得已而为之)
计算密集型: 一个进程里搞一个线程,然后搞多个进程,可以效率高, 但是浪费资源
io密集型: 占用cpu的时间不长,并发效率高
java,c# 多进程的并发(浪费资源,多线程可实现,基本不开多进程)
计算密集型:高
io密集型:高
6.为什么GIL锁有短板, 还要有这个东西
猜测:写的不够好, 当时开发的时候为了更快的开发出来没时间考虑
7.以后写python时
io操作型:用多线程 -- 文件/输入输出/socket网络通信
计算密集型:用多进程
五.python线程编写+锁
1.多线程时,线程切换的时机: cpu执行100个指令 (interval 间隔)
import sys
print(sys.getcheckinterval()) #100 python里面: cpu执行一个线程的100个cpu指令,就会切换另外一个线程
2.简单线程的编写
import threading
def func(arg):
print(arg)
t = threading.Thread(target=func,args=('666',))
t.start()
3.主线程默认: 等子线程执行完, 然后一起退出
import threading
import time
def func(arg):
time.sleep(arg)
print(arg)
t1 = threading.Thread(target=func,args=(3,))
t1.start()
t2 = threading.Thread(target=func,args=(5,))
t2.start()
print('123') #主线程打印完123, 没有终止, 而是等子线程运行完,一起退出
4.主线程: 不等我这个子线程(是否执行完), 主线程就先退出
如果子没完,那么dead
t1.setDeamon(True),
import threading
import time
def func(arg):
time.sleep(arg)
print(arg)
t1 = threading.Thread(target=func,args=(3,))
t1.setDaemon(True)
t1.start()
t2 = threading.Thread(target=func,args=(5,))
t2.setDaemon(True) #子线程若想执行完,就要跟上主线程的脚步,否则dead
t2.start()
print('123')
5.让开发者可以控制: 主线程站住别动,等我多少时间
t.join(): 无参数: 夯住, 一直等: 等我这个线程执行完, 再继续往下执行: 相当于不写多线程,不去并发
t2.join(2): 有参数: 最多夯住2秒钟, 如果我这个线程1秒执行完,那么就无须等2秒
import threading
import time
def func(arg):
time.sleep(arg)
print(arg)
print('创建子线程t1')
t1 = threading.Thread(target=func,args=(3,))
t1.start()
t1.join()
print('创建子线程t2')
t2 = threading.Thread(target=func,args=(5,))
t2.start()
t2.join(2)
print('123')
6.线程的名字
t1.setName(): 设置线程的名字
threading.current_thread().getName(): 获取当前线程的名字
import threading
import time
def func(arg):
time.sleep(arg)
t = threading.current_thread() #2.再获取当前线程的对象
print(t.getName()) #3.然后从线程对象中取到名字
print('创建子线程t1')
t1 = threading.Thread(target=func,args=(3,))
t1.setName('线程1') #1.1.先设置线程的名字
t1.start()
print('创建子线程t2')
t2 = threading.Thread(target=func,args=(5,))
t2.setName('线程2') #1.2.先设置线程的名字
t2.start()
print('123')
7.start()的解读
start()只是说告诉cpu我准备好了(就绪态), 什么时候执行cpu说了算
import threading
def func(arg):
print(arg)
t1 = threading.Thread(target=func,args=(666,))
t1.start()
print('123')
8.面向对象版本的多线程
8.1.方式一: 自定义一个类(继承threading.Thread),里面pass(这种比较常用)
import threading
class MyThread(threading.Thread):
pass
def func(arg):
print(arg)
t = MyThread(target=func, args=(666,))
t.start()
8.2.方式二: 自定义类中不加函数这个参数,默认执行类中的run()方法
import threading
class MyThread(threading.Thread):
def run(self):
print(123,self._args, self._kwargs)
t = MyThread(args=(123,)) #如果参数里不加函数func, 那么默认会执行threading.Thread类中的run()方法
t.start()
9.多线程的应用场景
9.1.对计算密集型的这种无用
import threading
v1 = [11,22,33]
v2 = [44,55,66]
def func(data,plus):
print([ i + plus for i in data])
t1 = threading.Thread(target=func, args=(v1,1))
t1.start()
t2 = threading.Thread(target=func, args=(v2,100)) #GIL锁住了 cpu , 这种是没办法并发, 没法提高效率的
t2.start()
9.2.有用的:比如之前的socket操作,是io密集型操作)
10.初识锁: 排队执行
import threading
import time
lock = threading.RLock() #创建锁
n = 10
def task(arg):
print('这段代码不加锁')
lock.acquire() #加锁(acquire 获得)
print('这段代码加锁') #这段代码加锁,执行临界资源,其他子进程需等待
global n
time.sleep(1)
print(arg, n)
n = arg
lock.release() #释放锁(release 释放)
for i in range(10):
t = threading.Thread(target=task, args=(i,))
t.start()
time.sleep(2)
print(n)
总结:
1.为什么要创建多线程?
java,c#:由于线程是cpu工作的最小单元,创建多线程可以利用多核优势实现并行操作
python: io密集型操作有并行优势,但不是多核优势实现的.
2.为什么要创建进程?
进程和进程之间做数据隔离
3.python 多线程的特殊性(GIL锁)
把进程中的多线程锁住了,所以不能利用多核优势
不得已:开多进程,必然浪费资源
4.线程的创建方式:
Thread
MyThread
5.线程其他
join
setDeamon
setName
threading.current_thread().getName()
6.锁
threading.Rlock().acquire()
threading.Rlock().release()