多任务:
初始:
多任务就是同一时刻多个任务同时执行,例如开演唱会时明星一边唱歌一边跳舞,开车时眼睛看路手操作方向盘。这些都是多任务场景。
对于电脑来说多任务就是同时运行多个应用程序,例如qq、微信、浏览器等等同时在电脑上运行。
- 电脑实现多任务的原理
例如qq、微信、网易云音乐播放器3个应用程序能同时运行是因为CPU在多个应用程序之间高速切换的结果,当CPU切换到了qq,就用0.01s时间(时间不确定)执行qq程序,然后再随机切换到其他应用程序在执行一段时间,CPU在多个程序之间快速往复执行,我们的肉眼根本感觉不到卡顿,导致我们的错觉感觉是同时运行的效果。如果电脑运行了多个程序有时候会出现卡顿现象是因为cup切换不过来了。
多任务就是同一时刻多个任务同时执行,例如开演唱会时明星一边唱歌一边跳舞,开车时眼睛看路手操作方向盘。这些都是多任务场景。
对于电脑来说多任务就是同时运行多个应用程序,例如qq、微信、浏览器等等同时在电脑上运行。
- 电脑实现多任务的原理
例如qq、微信、网易云音乐播放器3个应用程序能同时运行是因为CPU在多个应用程序之间高速切换的结果,当CPU切换到了qq,就用0.01s时间(时间不确定)执行qq程序,然后再随机切换到其他应用程序在执行一段时间,CPU在多个程序之间快速往复执行,我们的肉眼根本感觉不到卡顿,导致我们的错觉感觉是同时运行的效果。如果电脑运行了多个程序有时候会出现卡顿现象是因为cup切换不过来了。
拿程序看一下:
不使用多任务:
import time
def sing():
for i in range(3):
print('正在唱歌...', i)
time.sleep(1)
def dance():
for i in range(3):
print('正在跳舞...', i)
time.sleep(1)
def main():
sing()
dance()
if __name__ == '__main__':
main()
正在唱歌... 1
正在唱歌... 2
正在唱歌... 3
正在跳舞... 1
正在跳舞... 2
正在跳舞... 3
使用多任务:
import time, multiprocessing
def sing():
for i in range(3):
print('正在唱歌...', i + 1)
time.sleep(1)
def dance():
for i in range(3):
print('正在跳舞...', i + 1)
time.sleep(1)
def main():
# 创建进程
# target 指向的是执行的目标
p1 = multiprocessing.Process(target=sing)
p2 = multiprocessing.Process(target=dance)
# 开始进程
p1.start()
p2.start()
if __name__ == '__main__':
main()
正在唱歌... 1
正在跳舞... 1
正在唱歌... 2
正在跳舞... 2
正在唱歌... 3
正在跳舞... 3
多进程:
我们想通过酷我听歌,具体的过程应该是先找到酷我应用程序,然后双击就会播放音乐。
当我们双击的时候,操作系统将程序装载到内存中,操作系统为它分配资源,然后才能运行。运行起来的应用程序就称之为进程。也就是说当程序不运行的时候我们称之为程序,当程序运行起来他就是一个进程。通俗的理解就是不运行的时候是程序,运行起来就是进程。程序和进程的对应关系是:程序只有一个,但是进程可以有多个。
多进程queue队列:
在是用queue队列时,导入模块有两种方式:一、import multiprocessing 二、import queue
import multiprocessing
import queue
# 可以写参数,如果不写可以放任何数据
# 指定参输后,只能从存放指定的个数数据
q = queue.Queue(3) # ----->导入的是queue模块
# q = multiprocessing.Queue(3)# --------->导入的是multiprocessing模块
q.put(2)
q.put(4)
q.put(6)
# q.put(5) #------>阻塞了,知道数据有被使用后,才能继续被放入,否则一直等待放入
# q.put_nowait(8) # ---->不等待直接抛出异常
print(q.get()) # ----->get是获取队列中的值
print(q.get())
print(q.get())
# q.get() # 如果队列中没有了,则会一直等待,直到有数据才执行
# q.get_nowait() # 不等待,直接抛出异常
print(q.empty()) # 判断队列中是否还有数据,如果没有了则返回True
print(q.full()) # 判断队列是否满了,满了则返回True
使用多进程可以节约资源、并且可以传入参数:
import time
import multiprocessing
def sing(num):
for i in range(num):
print('正在唱歌...', i)
time.sleep(1)
def dance(num):
for i in range(num):
print('正在跳舞...', i)
time.sleep(1)
def main():
# 创建进程
# target 指向的是执行的目标 args 指向的是num的值
p1 = multiprocessing.Process(target=sing, args=(3,))
p2 = multiprocessing.Process(target=dance, args=(3,))
p1.start()
p2.start()
if __name__ == '__main__':
main()
正在唱歌... 0
正在跳舞... 0
正在唱歌... 1
正在跳舞... 1
正在唱歌... 2
正在跳舞... 2
进程间的通信:
刚才我们说了进程可以理解为复制了一份程序有加载到了内存了,进程之间是独立的,如果我想两个进程之间进行通讯怎么办呢?我们可以使用Queue 队列,队列是一种先进先出的存储数据结构,就比如排队上厕所一个道理。
两个进程通讯,就是一个子进程往queue中写内容,另一个进程从queue中取出数据。就实现了进程间的通讯了。
from multiprocessing import Process, Queue
# import time
def down_data(q):
lst = [1, 2, 3]
for i in lst:
q.put(i) # 将提取的内容放到队列中
def process_data(q):
for i in range(q.qsize()): # 获取队列的长度
print(q.get()) # 提取队列中的内容
def main():
# 创建一个队列
q = Queue()
# 创建两个子进程
q1 = Process(target=down_data, args=(q,))
q2 = Process(target=process_data, args=(q,))
q1.start()
# time.sleep(2)
q2.start()
if __name__ == '__main__':
main()
1
2
3
进程池:
当需要创建的子进程数量不多时,我们可以直接利用multiporcessing中的Process动态生成多个进程,但是如果现在有100个任务需要处理,那我们需要多少个子进程呢,如果我们创建100个子进程也可以实现,但是资源比较浪费。我们也可以创建指定个数个子进程,例如只创建10个子进程,让着10个子进程重复的执行任务,这样就节约了资源。
就比如我们去景区湖上游玩,游船是重复利用的。
import time
from multiprocessing import Process, Pool
def foo(i):
print('i的值是', i)
time.sleep(3)
print('end...')
if __name__ == '__main__':
# for i in range(5):
# p1 = Process(target=foo, args=(i,))
# p1.start()
pool = Pool(3) # 创建进程池对象
for i in range(5):
pool.apply_async(func=foo, args=(i,)) # 将任务添加到进程池中
pool.close() # 关闭进程池,等待所有进程结束后才关闭
pool.join() # 主进程等待所有子进程执行完毕后才执行主进程,必须在close后
i的值是 0
i的值是 1
i的值是 2
end...
i的值是 3
end...
i的值是 4
end...
end...
end...
线程:
由于进程是资源拥有者,创建、撤消与切换存在较大的内存开销,因此需要引入轻型进程
即线程,
进程是资源分配的最小单位,线程是CPU调度的最小单位(程序真正执行的时候调用的是线程).每一个进程中至少有一个线程。
使用Thread模块创建线程:
import time
import threading
def sing():
for i in range(3):
print('正在唱歌...', i + 1)
time.sleep(1)
def dance():
for i in range(3):
print('正在跳舞...', i + 1)
time.sleep(1)
def main():
t1 = threading.Thread(target=sing) # 创建t1子线程,执行sing函数
t2 = threading.Thread(target=dance)
t1.start() # 进程开始
t2.start()
if __name__ == '__main__':
main()
print('程序结束了...')
正在唱歌... 1
正在跳舞... 1
程序结束了...
正在唱歌... 2
正在跳舞... 2
正在跳舞... 3
正在唱歌... 3
传入参数:
import time
import threading
def sing(num): # 传参
for i in range(num):
print('正在唱歌...', i + 1)
time.sleep(1)
def dance(num):
for i in range(num):
print('正在跳舞...', i + 1)
time.sleep(1)
def main():
t1 = threading.Thread(target=sing, args=(3,)) # 创建t1子线程,执行sing函数
t2 = threading.Thread(target=dance, args=(3,))
t1.start() # 进程开始
t2.start()
if __name__ == '__main__':
main()
print('程序结束了...')
join方法:
join方法:只有等待一个子线程结束完以后,才能继续运行下一个子线程
import time, threading
def sing():
for i in range(3):
print('正在唱歌...', i)
time.sleep(1)
def dance():
for i in range(3):
print('正在跳舞...', i)
time.sleep(1)
def main():
t1 = threading.Thread(target=sing) # 创建t1子线程,执行sing函数
t2 = threading.Thread(target=dance)
t1.start() # 进程开始
t1.join() # t1子线程执行完后,t2和主线程同时执行
t2.start()
# t1.join() # 效果:t1,t2子程序执行完后,主线程才开始执行
if __name__ == '__main__':
main()
print('程序结束了...')
.setDaemon() 方法:
setDaemon方法:用来守护主线程,当主线程执行完毕后,子线程不会继续执行
如果设置一个子线程守护,那么程序将会正常运行
import time,threading
def sing():
for i in range(3):
print('正在唱歌...', i)
time.sleep(1)
def dance():
for i in range(3):
print('正在跳舞...', i)
time.sleep(1)
def main():
t1 = threading.Thread(target=sing) # 创建t1子线程,执行sing函数
t2 = threading.Thread(target=dance)
t1.setDaemon(True) # 设置t1、t2守护主线程,当主线程执行完后,子线程不会继续往下执行
# t2.setDaemon(True)
t1.start() # 进程开始
t2.start()
if __name__ == '__main__':
main()
print('程序结束了...')
实例方法:
- getName(): 获取线程的名称。
- setName(): 设置线程的名称。
- isAlive(): 判断当前线程存活状态。
import time,threading
def sing(num): # 传参
for i in range(num):
print('正在唱歌...', i + 1)
time.sleep(1)
def dance(num):
for i in range(num):
print('正在跳舞...', i + 1)
time.sleep(1)
def main():
t1 = threading.Thread(target=sing, args=(3,))
t2 = threading.Thread(target=dance, args=(3,))
t1.start()
print(t1.is_alive()) # ---->True
t2.start()
# t1.setName('11') # 更改线程名称
print(t1.getName()) # ---->Thread-1
if __name__ == '__main__':
main()
print('程序结束了...')
使用继承开启线程:
1.定义一个类继承threading.Thread类。
2.复写父类的run()方法。
import threading
class MyThraed(threading.Thread):
def __init__(self, num):
super().__init__()
self.num = num
def run(self):
for i in range(self.num):
print('...run...', i)
if __name__ == '__main__':
mythread = MyThraed(3)
mythread.start()
...run... 0
...run... 1
...run... 2
线程之间共享全局变量:
import threading, time
g_num = 10
def test1():
global g_num
g_num += 1
print('test1---->', g_num)
def test2():
print('test2---->', g_num)
def main():
t1 = threading.Thread(target=test1)
t2 = threading.Thread(target=test2)
t1.start()
time.sleep(1)
t2.start()
if __name__ == '__main__':
main()
test1----> 11
test2----> 11
共享全局变量问题:
多线程开发的时候共享全局变量会带来资源竞争效果。也就是数据不安全。
当数据足够大时会丢失数据,那我们来看一下:
#当传入参数为100时:
import threading, time
num = 0
def test1(num1):
global num
for i in range(num1):
num += 1
print('test1---->', num)
def test2(num1):
global num
for i in range(num1):
num += 1
print('test1---->', num)
def main():
t1 = threading.Thread(target=test1, args=(100,))
t2 = threading.Thread(target=test2, args=(100,))
t1.start()
time.sleep(1)
t2.start()
if __name__ == '__main__':
main()
#结果为:
test1----> 100
test1----> 200
当数据变为10000000时:
import threading, time
num = 0
def test1(num1):
global num
for i in range(num1):
num += 1
print('test1---->', num)
def test2(num1):
global num
for i in range(num1):
num += 1
print('test1---->', num)
def main():
t1 = threading.Thread(target=test1, args=(10000000,))
t2 = threading.Thread(target=test2, args=(10000000,))
t1.start()
time.sleep(1)
t2.start()
if __name__ == '__main__':
main()
# 结果为:
test1----> 10384512
test1----> 18449412
由此我们可以看出当数据足够大时,数据会丢失。
那么我们怎么进行解决呢,可以使用互斥锁将数据“锁起来”,这样数据就不会丢失了。
import threading, time
num = 0
def test1(num1):
global num
lock.acquire() # 加锁
for i in range(num1):
num += 1
lock.release() # 解锁
print('test1---->', num)
def test2(num1):
global num
lock.acquire()
for i in range(num1):
num += 1
lock.release()
print('test1---->', num)
def main():
t1 = threading.Thread(target=test1, args=(10000000,))
t2 = threading.Thread(target=test2, args=(10000000,))
t1.start()
time.sleep(1)
t2.start()
lock = threading.Lock() # 创建锁对象
if __name__ == '__main__':
main()
test1----> 10000000
test1----> 20000000
协程:
greenlet 模块:
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
安装 :pip3 install greenlet。如果引入的时候还是报错,使用pycharm进行下载安装,
选择 file --> settings --> Project:项目名称 --> Project Interpreter-->…
import greenlet
import time
def test1():
while True:
print('---test1---')
time.sleep(0.5)
g2.switch() # 2.当执行完test1 会去执行test2
def test2():
while True:
print('---test2---')
time.sleep(0.5)
g1.switch() # 3.当执行完test2 会去执行test1
if __name__ == '__main__':
g1 = greenlet.greenlet(test1) # 指定g1的执行目标
g2 = greenlet.greenlet(test2)
g1.switch() #1. 让程序去g1指定的目标去执行
gevent模块:
import gevent
import time
from gevent import monkey
# 所有;补丁
monkey.patch_all()
# 如果程序中没有耗时操作就顺序执行
def test1():
for i in range(0, 5):
print('test1------>', i)
time.sleep(0.5)
def test2():
for i in range(0, 5):
print('test2------>', i)
time.sleep(0.5)
if __name__ == '__main__':
gevent.joinall([gevent.spawn(test1),
gevent.spawn(test2)])
结果为:
test1------> 0
test2------> 0
test1------> 1
test2------> 1
test1------> 2
test2------> 2
test1------> 3
test2------> 3
test1------> 4
test2------> 4
协程小拓展:图片加载器
import requests
import gevent
# url = 'https://b-ssl.duitang.com/uploads/item/201612/10/20161210005234_hZzLn.jpeg'
# response = requests.get(url)
# # response.content # 获取图片二进制数据
#
# with open('img/1.jpg', mode='wb')as f:
# f.write(response.content)
def downloader(url, name):
response = requests.get(url)
with open('img/' + name, mode='wb')as f:
f.write(response.content)
if __name__ == '__main__':
url = 'https://b-ssl.duitang.com/uploads/item/201806/16/20180616111135_gnmcq.jpeg'
url1 = 'https://b-ssl.duitang.com/uploads/item/201812/30/20181230142429_qmkvs.jpg'
gevent.joinall([
gevent.spawn(downloader, url, '寻欢作乐.jpeg'),
gevent.spawn(downloader, url1, '暴富.jpg')
])