什么是多任务
- 简单地说,就是同时可以运行多个任务。
一、线程
- 提问?
如果一个程序想同时执行多个部分的代码,那么基本有2种方式实现:
线程
进程
- 什么是线程
我们把运行的代码可以当作一个河流主干流,而线程就是一个分支运行流程,最后和主干流合并。
- 多线程执行
import threading
import time
def say_sorry():
print("亲爱的,我错了,我能吃饭了吗?")
time.sleep(1)
for i in range(5):
t = threading.Thread(target=say_sorry) # 创建线程
t.start() # 启动线程,即让线程开始执行
- 创建线程的另外一种方式
上面创建线程的基本流程是:
使用threading.Thread()创建一个对象,并且在使用target来指定一个函数,作为线程要执行的代码
- 示例代码
import threading
import time
class MyThread(threading.Thread):
def run(self):
for i in range(3):
msg = "I'm "+ self.name + ' @ '+str(i) # name属性中保存的是当前线程的名字
print(msg)
time.sleep(1)
if __name__ == '__main__':
t = MyThread()
t.start()
封装性更好的一种创建线程的方式是:
定义一个新的类,继承Thread类
在这个类中实现run方法
在run方法中写如要执行的代码
当使用这个类创建一个对象后,调用对象的start方法就可以让这个线程执行,且会自动执行run方法的代码
队列(Queue)
- 什么是队列
一种特殊的存储数据的方式,可以实现先存入的数据,先出去。
- 代码演示
import queue
q = queue.Queue()
q.put('11') # 存入字符串
q.put(22) # 存入整数
q.put({'num': 100}) # 存入字典
print(q.get()) # 11
print(q.get()) # 22
print(q.get()) # {'num': 100}
- 总结
先进先出(FIFO)
可以存放任意类型数据
- 堆栈Queue
import queue
q = queue.LifoQueue()
q.put('11') # 存入字符串
q.put(22) # 存入整数
q.put({'num': 100}) # 存入字典
print(q.get()) # {'num': 100}
print(q.get()) # 22
print(q.get()) # 11
- 总结
后进先出(LIFO)
可以存放任意数据类型
- 优先级Queue
import queue
q = queue.PriorityQueue()
q.put((10, 'Q'))
q.put((30, 'Z'))
q.put((20, 'A'))
print(q.get()) # (10, 'Q')
print(q.get()) # (20, 'A')
print(q.get()) # (30, 'Z')
- 总结
存放的数据是元组类型,第1个元素表示优先级,第2个元素表示存储的数据
优先级数字越小优先级越高
数据优先级高的优先被取出
用于VIP用户数据优先被取出场景,因为上面两种都要挨个取出
多线程的优缺点
优点:
在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据
缺点:
线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)
如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确
互斥锁
为什么要用互斥锁
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
互斥锁的作用
互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
- 使用互斥锁
threading模块中定义了Lock类,可以方便的使用:
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
锁的好处:
确保了某段关键代码同时只能由一个线程从头到尾完整地执行
锁的坏处:
阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁
进程
- 什么是进程
一个程序运行起来后,代码+用到的资源 称之为进程,它是操作系统分配资源的基本单元。
- 实现多任务
在python中使用使用进程实现多任务的方式有3种:
1.创建Process对象
2.基础Process类,创建自己的对象,实现run翻番
3.使用进程池
- 创建进程方式1
from multiprocessing import Process
import time
def test():
"""子进程单独执行的代码"""
while True:
print('---test---')
time.sleep(1)
if __name__ == '__main__':
p=Process(target=test)
p.start()
# 主进程单独执行的代码
while True:
print('---main---')
time.sleep(1)
每个进程都有1个数字来标记,这个数字称之为进程号
Linux系统中查看PID的命令是ps
- Process创建的实例对象的常用方法:
start():启动子进程实例(创建子进程)
is_alive():判断子进程是否还在活着
join([timeout]):是否等待子进程执行结束,或等待多少秒
terminate():不管任务是否完成,立即终止子进程
- 且, 进程不同享全局变量
进程、线程对比
- 通俗理解进程、线程
进程,能够完成多任务,比如 在一台电脑上能够同时运行多个QQ
线程,能够完成多任务,比如 一个QQ中的多个聊天窗口
- 区别:
一个程序至少有一个进程,一个进程至少有一个线程.
线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性高。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
线线程不能够独立执行,必须依存在进程中优缺点
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。