概念
一、多任务
就是多个任务同时执行
程序中:任务就可以指的是函数,一个函数就是一个任务,如果想多个任务同时执行,就要使用多进程、多线程来实现
同步、异步、并发、并行
同步:
执行完任务A,才能执行B任务
异步:
执行任务A的同时可以执行任务B
并发:
伪异步,任务a和任务b在同一时刻不是同时运行,但是在一段时间范围内,都在运行(交替执行任务)
通过时间切片,CUP不同的切换,由于速度快,所以就会像是在同时执行,(这里还有一个上下文概念和寄存器在这里不做过多解释)并行:
真异步,任务a和任务b真的在同时运行()
关于并发和并行的比较
区别:是否是同时处理任务
并发:交替执行任务
并发的关键是你有处理多个任务的能力,不一定要同时。
并行:同时执行任务
并行的关键是你有同时处理多个任务的能力,强调的是同时
并发表示同时发生了多件事情,通过时间片切换,哪怕只有单一的核心,也可以实现“同时做多件事情”这个效果
并发是独立执行过程的组合,而并行是同时执行计算
并发是一次处理很多事情,并且是同时做很多事情
并行才是我们通常认为的那个同时做很多事情,而并行则是在线程这个模型下产生的概念。并发表示同时发生了很多事情,通过时间切片,哪怕只有单一的核心,也可以实现“同时做多个事情”这个效果。根据底层是否有多个处理器,兵法与并行是可以等效的,这并不是连个互斥的概念。
举个例子:
开发中说资源请求并发数达到了1万。这里的意思是:有一万个请求同时过来了。但是这里很明显不可能真正的同时处理1万个请求。如果这台计算机的处理器有4 个核,不考虑超线程,那么我们认为同时会有4个线程在跑。也就是说:并发访问数是一万,而底层真实的并行处理的请求数是4 如果并发数小一些只有4的话,又或者你的机器牛逼有1万个核心,那并发在这里和并行一个效果。也就是说,并发可以是虚拟的同时执行,也可以是真的同时执行。而并行的意思是真的同时执行
结论是:并行是我们屋里时空观下的同时执行,而并发则是操作系统用线程这个模型抽象之后站在线程的视角看到的“同时”执行
一个多核cpu在处理多个任务的时候如果想要发挥最大功效就要实现并行
多进程可以充分使用cpu的两个内核,而多线程却不能充分使用cpu的两个内核,多线程并不能真正的让多核cpu实现并行
原因:
cpython解释器中存在一个GIL(全局线程解释器锁),它的作用就是保证同一时刻只有一个线程可以执行代码,因此造成了使用多线程的时候无法实现并行。
什么是GIL
Python语言和GIL没有半毛钱关系。仅仅是由于历史原因在Cpython虚拟机(解释器),难以移除GIL。
GIL:全局解释器锁。每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码。
线程释放GIL锁的情况: 在IO操作等可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后,必须重新获取GIL Python 3.x使用计时器(执行时间达到阈值后,当前线程释放GIL)或Python 2.x,tickets计数达到100
Python使用多进程是可以利用多核的CPU资源的。
多线程爬取比单线程性能有提升,因为遇到IO阻塞会自动释放GIL锁
结论:
1. 在 处理像科学计算 这类需要持续使用cpu的任务的时候 单线程会比多线程快
2. 在 处理像IO操作等可能引起阻塞的这类任务的时候 多线程会比单线程
二、进程
进程如何理解
一个程序至少有一个进程,一个进程至少有一个线程
进程在执行的过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
进程:是系统进行资源分配和调度的一个独立单位
电脑中:一个软件,就是一个进程,windows里面有进程管理器,linux里面通过 kill -9 进程id号
代码中:你写了一个py代码,代码在没有运行之间称之为程序,代码运行起来就是一个进程
如果只有一个进程,我们称之为主进程,如果还有其它进程,肯定是通过主进程创建的子进程
进程之间是否共享局部变量
不共享
进程之间是否共享全局变量
不共享
(1)进程创建方式
面向过程创建
p = Process(target=xxx, args=(xxx,))
target: 进程启动要执行的方法
args: 主进程给子进程要传递的参数
p.start(): 启动进程
p.join(): 让主进程等待
from multiprocessing import Process
import time
import os
def sing(a, b):
print('%s比%s漂亮吗' % (b, a))
print('sing进程的id号为--%s--' % os.getpid())
print('sing进程的父进程id号为--%s--' % os.getppid())
for x in range(1, 6):
print('我在唱女儿情')
time.sleep(1)
def dance():
print('dance进程的id号为--%s--' % os.getpid())
print('dance进程的父进程id号为--%s--' % os.getppid())
for x in range(1, 6):
print('我在跳广场舞')
time.sleep(1)
def main():
print('主进程的id号为--%s--' % os.getpid())
name1 = '大道东'
name2 = '哈哈哈'
# 主进程在这里,创建两个子进程
p_sing = Process(target=sing, args=(name1, name2))
p_dance = Process(target=dance)
# 启动进程
p_sing.start()
p_dance.start()
# 让主进程等待子进程结束之后再结束
p_sing.join()
p_dance.join()
print('主进程运行结束')
if __name__ == '__main__':
main()
2)面向对象创建
class MyProcess(Process):
def __init__(self, name):
super().__init__()
def run():
pass
from multiprocessing import Process
import time
class SingProcess(Process):
def __init__(self, name):
# 手动调用父类的构造方法
super().__init__()
self.name = name
# 重写父类的方法,启动之后直接调用
def run(self):
print('主进程传递过来的参数为%s' % self.name)
for x in range(1, 6):
print('我在唱小幸运')
time.sleep(1)
class DanceProcess(Process):
def run(self):
for x in range(1, 6):
print('我在跳拉丁舞')
time.sleep(1)
def main():
name = '柳岩'
p_sing = SingProcess(name)
p_dance = DanceProcess()
p_sing.start()
p_dance.start()
p_sing.join()
p_dance.join()
print('主进程-子进程全部结束')
if __name__ == '__main__':
main()
进程池
进程是不是越多越好呢?
创建进程肯定需要空间的。切换也需要时间和资源
格式
from multiprocessing import Pool
p = Pool(3)
p.apply_async(xxx, args=(xxx,)) 添加任务
p.close() 添加任务完毕,关闭池子
p.join() 让主进程等待
进程池
from multiprocessing import Pool
import os
import time
import random
def demo(name):
print('%s--任务正在执行,进程id号为--%s--' % (name, os.getpid()))
lt = [2, 3, 4]
time.sleep(random.choice(lt))
def main():
# 创建进程池 代表的意思就是最多创建3个进程
pol = Pool(3)
# 给池子添加任务
names_list = ['关羽', '张飞', '赵云', '马超', '黄忠', '张辽', '典韦', '许褚', '夏侯惇', '于禁']
for name in names_list:
pol.apply_async(demo, args=(name,))
# 关闭池子,不允许添加任务了
pol.close()
# 让主进程等待
pol.join()
print('主进程-子进程全部结束')
if __name__ == '__main__':
main()
线程
是进程的一个实体,是CPU调度和分派的基本单位,比进程更小的能独立的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但是他可以与同属一个进程的其他线程共享进程所有资源
进程与线程的关系
线程属于进程,一个线程只能属于一个进程,一个进程可以拥有多个线程
系统分配资源的时候,以进程为单位分配
系统执行的时候,以线程为单位执行
一个程序至少有一个进程,一个进程至少有一个线程,
线程不能够独立执行,必须依存在进程中
线程的资源比进程少,使得多线程程序的并发性高
进程在执行的过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
多线程在解决高并发问题中所起到的作用就是:
使计算机的资源在每一时刻都能达到最大的利用率,不至于浪费计算机资源使其闲置
优缺点
线程执行开销小,但不利于资源管理和保护,不稳定(一个线程子线程挂了,影响整个主线程)
进程执行开销大,好管理,稳定(一个子进程挂了不影响整个进程)
若调用run,相当于正常的函数调用,将按照程序的顺序执行
若调用start,则先执行主进程,后执行子进程
若调用run,相当于正常的函数调用,将按照程序的顺序执行
面向过程创建线程
import threading
import time
def sing(a):
print('sing线程的名字为%s' % threading.current_thread().name)
print('传递过来的参数为%s' % a)
for x in range(1, 6):
print('我在唱女儿情')
time.sleep(1)
def dance():
print('dance线程的名字为%s' % threading.current_thread().name)
for x in range(1, 6):
print('我在跳广场舞')
time.sleep(1)
def main():
name = '马尔扎哈'
print('主线程的名字为%s' % threading.current_thread().name)
# 创建线程
t_sing = threading.Thread(target=sing, name='唱歌', args=(name,))
t_dance = threading.Thread(target=dance)
# 启动线程
t_sing.start()
t_dance.start()
# 让主线程等待
t_sing.join()
t_dance.join()
print('主线程-子线程全部运行结束')
if __name__ == '__main__':
main()
多线程执行同一个任务
经测试比正常的 快一倍
def func():
a = requests.get(url).text
if __name__ == '__main__':
task = []
for i in range(400):
t = threading.thread(targer=func)
t.start()
task.append(t)
for i in task:
i.join()
面向对象创建线程
import threading
import time
class SingThread(threading.Thread):
def __init__(self, a):
super().__init__()
self.a = a
# 就是给当前线程起名字的
self.name = '高尔夫'
def run(self):
print('传递过来的参数为%s' % self.a)
print('当前线程的名字为%s' % threading.current_thread().name)
for x in range(1, 6):
print('我在唱女儿情')
time.sleep(1)
class DanceThread(threading.Thread):
def run(self):
for x in range(1, 6):
print('我在跳广场舞')
time.sleep(1)
def main():
a = '关之琳'
# 创建线程
t_sing = SingThread(a)
t_dance = DanceThread()
# 启动线程
t_sing.start()
t_dance.start()
# 让主线程等待
t_sing.join()
t_dance.join()
print('主线程-子线程全部运行结束')
if __name__ == '__main__':
main()
注:
线程之间是否共享局部变量
不共享
线程之间是否共享全局变量
共享
四、队列
队列:排队买饭,排队买票
特点:先进先出
栈:先进后出,子弹,函数的调用过程
格式
from queue import Queue
q = Queue(5)
q.put(xxx)
q.put(xxx, False) 队列满,立即抛出异常
q.put(xxx, True, 5) 队列满,5s之后抛出异常
q.get()
q.get(False) 队列空,立即抛出异常
q.get(True, 5) 队列空,5s之后抛出异常
q.full() 是否满
q.empty() 是否空
q.qsize() 获取队列元素的个数
from queue import Queue
# 创建一个队列
# 5代表里面只能放5个元素,可以不写,不写代表无限长
q = Queue(5)
print(q.full())
print(q.empty())
print(q.qsize())
# 向队列中添加元素
q.put('林霞')
q.put('宋英')
q.put('王贤')
print(q.full())
print(q.empty())
print(q.qsize())
q.put('智')
q.put('林娇')
# q.put('章泽天', True, 5)
# 从队列中获取元素
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())
# print(q.get(True, 5))
五、锁
为什么会有锁的概念?
原因:进程之间不管是全局变量还是局部变量不会共享,但是线程之间共享全局变量,一个线程在利用资源的时候,其他线程就不能用,所以要保证同一时刻只有一个线程在执行任务
加锁、释放锁,同一把锁
from threading import Lock
lock = Lock() 创建一把锁
lock.acquire() 上锁
lock.release() 释放锁
多线程实现同步的四种方式
https://blog.csdn.net/sunhuaqiang1/article/details/69389316?utm_source=blogxgwz2
信号量
https://blog.csdn.net/a349458532/article/details/51589460?utm_source=blogxgwz0