目录
一、操作系统
二、多任务
1.电脑实现多任务的原理
单核cpu
双核/多核cpu
2.查看cpu
三、进程
1.什么是进程
2.创建进程
3.进程的状态
4.进程之间的通信
(1)引入
(2)队列queue
(3)传递参数
(4)通信
5.进程池
四、线程
1.线程概念
2.进程和线程之间的关系
3.使用threading模块
(1)线程引入
(2)传递参数
(3)join方法
(4)setDaemon方法
(5)实例方法
(6)threading模块中的方法
4.使用继承方式开启线程
5.多线程共享变量
6.共享全局变量的问题
7.同步异步
(1)同步
(2)异步
8.互斥锁
9.死锁
10.线程队列Queue
11.生产者与消费者模型
12.GIL全局解释锁
操作系统(英语:operating system,缩写作 OS)是管理计算机硬件与软件资源的计算机程序,同时也是计算机系统的内核与基石。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入与输出设备、操作网络与管理文件系统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。
操作系统:Dos Windows ios android linux unix
同一时间多个任务同时进行,例如明星开演唱会的时候边唱歌边跳舞。
对于电脑来说多任务就是同时运行多个程序,例如qq,微信,浏览器等同时在电脑上运行。
一个头一个大脑运算
同一时间只有一个程序能够运行
之所以看起来电脑同时能执行多个任务,是因为cpu高速切换的原理,肉眼根本不可见,其实实际上同一时间戳只有一个程序能够运行
一个头多个大脑运算
单核 双核
计算机---->右键属性---->设备管理器---->处理器
当程序运行起来的时候就是一个进程,当程序不运行的时候,就是一个程序。
例如:登上QQ运行,QQ就是进程,当退出QQ,QQ就是一个程序。
一个程序对应多个进程
进程是系统进行资源分配和调度的基本单位
ctrl+shift+esc打开windows任务管理器
import time
import multiprocessing
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():
#创建两个进程
#执行target目标
p1=multiprocessing.Process(target=sing)
p2=multiprocessing.Process(target=dance)
p1.start()
p2.start()
if __name__=='__main__':
main()
print('程序结束')
当所有子进程执行完后,主进程才会结束
在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。
(1)就绪(Ready)状态
当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
(2)执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。
(3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
import time
from multiprocessing import Process,Queue
def download_data(q):
lst=['a','b','c']
for i in lst:
q.put(i)#将下载的数据保存到队列中
print('下载完了数据')
def process_data(q):
for i in range(q.qsize()):
print(q.get(i))
print('处理完了数据')
def main():
#先创建一个队列
q=Queue()
#创建两个子进程
q1=Process(target=download_data,args=(q,))
q2=Process(target=process_data,args=(q,))
q1.start()#谁先抢到cpu谁先执行
q2.start()
if __name__=='__main__':
main()
子进程谁先抢到cpu谁就先执行
利用time.sleep()固定谁先执行
当创建的子进程不多的时候,可以直接用multiprocessing模块创建子进程,但如果子进程太多了,用multiprocessing模块创建就会浪费资源。我们可以创建指定个数子进程,例如只创建10个子进程,让这10个子进程重复进行,节约资源。
由于进程是资源拥有者,由于创建、撤销与切换存在较大的内存开销,因此需要引入轻型进程即线程,进程是资源分配的最小单位,线程是cpu调度的最小单位,程序真正执行的时候调用的是线程,每个进程至少拥有一个线程。
import time
import threading
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():
t1=threading.Thread(target=sing,args=(3,))#创建t1子线程
t2=threading.Thread(target=dance,args=(3,))
t1.start() #开启子线程
t2.start()
if __name__=='__main__':
main()
print('程序结束了')
join()方法功能:当前线程执行完后其他线程才会继续执行。
setDaemon()将当前线程设置成守护线程来守护主线程:
-当主线程结束后,守护线程也就结束,不管是否执行完成。
-应用场景:qq 多个聊天窗口,就是守护线程。
注意:需要在子线程开启的时候设置成守护线程,否则无效。
第一种
第二种
线程对象的一些实例方法,了解即可
- setName(): 设置线程的名称。
- getName(): 获取线程的名称。
- is_alive()或isAlive(): 判断当前线程存活状态。
threading.current_thread()返回当前的线程变量
threading.enumerate()返回一个目前正在运行的线程变量的列表
threading.active_count()返回还未结束的线程的个数
import threading,time
#1.继承threading.Thread类
class MyThread(threading.Thread):
def __init__(self,num):
super().__init__()#调用父类的__init__方法
self.num=num
#2.复写父类的run方法
def run(self):
for i in range(self.num):
print('i--->',i)
time.sleep(1)
if __name__=='__main__':
my_thread=MyThread(3)
my_thread.start()
多线程开发的时候共享全局变量会带来资源竞争效果。也就是数据不安全。
同步的意思就是协同步调,按预定的先后次序执行。例如你先说完然后我再说。
大家不要将同步理解成一起动作,同步是指协同、协助、互相配合。
例如线程同步,可以理解为线程A和B一块配合工作,A执行到一定程度时要依靠B的某个结果,于是停下来示意B执行,B执行完将结果给A,然后A继续执行。
A强依赖B(对方),A必须等到B的回复,才能做出下一步响应。即A的操作(行程)是顺序执行的,中间少了哪一步都不可以,或者说中间哪一步出错都不可以。
举个例子:
你去外地上学(人生地不熟),突然生活费不够了;此时你决定打电话回家,通知家里转生活费过来,可是当你拨出电话时,对方一直处于待接听状态(即:打不通,联系不上),为了拿到生活费,你就不停的oncall、等待,最终可能不能及时要到生活费,导致你今天要做的事都没有完成,而白白花掉了时间。
异步则相反,A并不强依赖B,A对B响应的时间也不敏感,无论B返回还是不返回,A都能继续运行;B响应并返回了,A就继续做之前的事情,B没有响应,A就做其他的事情。也就是说A不存在等待对方的概念。
举个例子:
在你打完电话发现没人接听时,猜想:对方可能在忙,暂时无法接听电话,所以你发了一条短信(或者语音留言,亦或是其他的方式)通知对方后便忙其他要紧的事了;这时你就不需要持续不断的拨打电话,还可以做其他事情;待一定时间后,对方看到你的留言便回复响应你,当然对方可能转钱也可能不转钱。但是整个一天下来,你还做了很多事情。 或者说你找室友临时借了一笔钱,又开始happy的上学时光了。
对于多线程共享全局变量计算错误的问题,我们可以使用线程同步来进行解决。
当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个线程安全的访问竞争资源(全局内容),最简单的同步机制就是使用互斥锁。
某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定状态,其他线程就能更改,直到该线程将资源状态改为非锁定状态,也就是释放资源,其他的线程才能再次锁定资源。互斥锁保证了每一次只有一个线程进行写入操作。从而保证了多线程下数据的安全性。
在多个线程共享资源的时候,如果两个线程分别占有一部分资源,并且同时等待对方的资源,就会造成死锁现象。
如果锁之间相互嵌套,就有可能出现死锁。因此尽量不要出现锁之间的嵌套。
队列是一种先进先出(FIFO)的存储数据结构,就比如排队上厕所一个道理。
1.创建一个“队列”对象
import Queue # 导入模块
q = Queue.Queue(maxsize = 10)
Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。
2.将一个值放入队列中 q.put(10)
调用队列对象的put()方法在队尾插入一个项目。
3.将一个值从队列中取出q.get()
从队头删除并返回一个项目。如果取不到数据则一直等待。
4.q.qsize() 返回队列的大小
5.q.empty() 如果队列为空,返回True,反之False
6.q.full() 如果队列满了,返回True,反之False
7.q.put_nowait(item) ,如果取不到不等待,之间抛出异常。
8.q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
9.q.join() 收到q.task_done()信号后再往下执行,否则一直等待。或者最开始时没有放数据join()不会阻塞。
q.task_done() 和 q.join() 通常一起使用。
例如A是爬取的数据,B是处理数据的,如果A爬取的快,B处理的慢,那么A就必须等B处理完才能继续爬取数据;
如果B处理数据能力大于A,那么B就必须等待A。为了解决这个问题就引入了生产者和消费者模式。
(1)什么是生产者消费者模式
生产者和消费者之间不直接通信,而是通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
import queue,threading
q=queue.Queue()
lock=threading.Lock()
def producer(name):
count=1
while count<=100:
q.join()#等待task_done()发送的信号
lock.acquire()
q.put(count)
print('{}正在做第{}碗面条'.format(name,count))
count+=1
lock.release()
def customer(name):
count=1
while count<=100:
data=q.get()
lock.acquire()
print('{}正在吃第{}碗面条'.format(name,data))
count+=1
lock.release()
q.task_done()#吃完后发送信号
def main():
t1=threading.Thread(target=producer,args=('赵四',))
t2=threading.Thread(target=customer,args=('小宝',))
t1.start()
t2.start()
if __name__=='__main__':
main()
GIL 即 :global interpreter lock 全局解释锁。
在进行GIL讲解之前,我们可以先了解一下并行和并发:
并行
多个CPU同时执行多个任务,就好像有两个程序,这两个程序是真的在两个不同的CPU内同时被执行。
并发
CPU交替处理多个任务,还是有两个程序,但是只有一个CPU,会交替处理这两个程序,而不是同时执行,只不过因为CPU执行的速度过快,而会使得人们感到是在“同时”执行,执行的先后取决于各个程序对于时间片资源的争夺。
并行和并发同属于多任务,目的是要提高CPU的使用效率。这里需要注意的是,一个CPU永远不可能实现并行,即一个CPU不能同时运行多个程序。
Guido van Rossum(吉多·范罗苏姆)创建python时就只考虑到单核cpu,解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁, 于是有了GIL这把超级大锁。因为cpython解析只允许拥有GIL全局解析器锁才能运行程序,这样就保证了同一个时刻只允许一个线程可以使用cpu。也就是说多线程并不是真正意义上的同时执行。