进程
1、什么是进程(process)?
定义:1)进程是资源分配最小单位
2)当一个可执行程序被系统执行(分配内存资源)就变成了一个进程
1. 程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,这种执行的程序就称之为进程
2. 程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念
3. 在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。
4. 进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
5. 进程之间有自己独立的内存,各进程之间不能相互访问
6. 创建一个新线程很简单,创建新进程需要对父进程进行复制
多道编程: 在计算机内存中同时存放几道相互独立的程序,他们共享系统资源,相互穿插运行
单道编程: 计算机内存中只允许一个的程序运行
进程并发性:
1)在一个系统中,同时会存在多个进程被加载到内存中,同处于开始到结束之间的状态
2)对于一个单CPU系统来说,程序同时处于运行状态只是一种宏观上的概念
他们虽然都已经开始运行,但就微观而言,任意时刻,CPU上运行的程序只有一个
3)由于操作系统分时,让每个进程都觉得自己独占CPU等资源
注:如果是多核CPU(处理器)实际上是可以实现正在意义的同一时间点有多个线程同时运行
线程并发性:
1)操作系统将时间划分为很多时间段,尽可能的均匀分配给每一个线程。
2)获取到时间片的线程被CPU执行,其他则一直在等待,所以微观上是走走停停,宏观上都在运行。
多核CPU情况:
如果你的程序的线程数少于CPU的核心数,且系统此时没有其他进程同时运行,那么这个程序的每个线程会享有一个CPU,
当同时运行的线程数多于CPU核心数时,CPU会采用一定的调度算法每隔一段时间就将这些线程调入或调出CPU
以确保每个线程都能分享一部分CPU时间,实现多线程并发。
2、有了进程为什么还要线程?
1. 进程优点:
提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率
2. 进程的两个重要缺点
a. 第一点:进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
b. 第二点:进程在执行的过程中如果阻塞,即使进程中有些工作不依赖于输入的数据,也将无法执行(例如等待输入,整个进程就会挂起)。
c. 例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻
即能监听键盘输入、又能监听其它人给你发的消息
d. 你会说,操作系统不是有分时么?分时是指在不同进程间的分时呀
e. 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀
线程
1、什么是线程(thread)(线程是操作系统最小的调度单位)
定义:1)线程是操作系统调度的最小单位
2)它被包含在进程之中,是进程中的实际运作单位
3)进程本身是无法自己执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合
1. 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位
2. 一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
3. 无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
4. 进程本身是无法自己执行的,要操作cpu,必须创建一个线程,线程是一系列指令的集合
5. 所有在同一个进程里的线程是共享同一块内存空间的,不同进程间内存空间不同
6. 同一个进程中的各线程可以相互访问资源,线程可以操作同进程中的其他线程,但进程仅能操作子进程
7. 两个进程想通信,必须要通过一个中间代理
8. 对主线程的修改可能回影响其他子线程,对主进程修改不会影响其他进程因为进程间内存相互独立,但是同一进程下的线程共享内存
2、进程和线程的区别
启动一个线程比启动一个进程快,运行速度没有可比性。
先有一个进程然后才能有线程。
1、进程包含线程
2、线程共享内存空间
3、进程内存是独立的(不可互相访问)
4、进程可以生成子进程,子进程之间互相不能互相访问(相当于在父级进程克隆两个子进程)
5、在一个进程里面线程之间可以交流。两个进程想通信,必须通过一个中间代理来实现
6、创建新线程很简单,创建新进程需要对其父进程进行克隆。
7、一个线程可以控制或操作同一个进程里面的其它线程。但进程只能操作子进程。
8、父进程可以修改不影响子进程,但不能修改。
9、线程可以帮助应用程序同时做几件事
3、进程和程序的区别
1. 程序只是一个普通文件,是一个机器代码指令和数据的集合,所以,程序是一个静态的实体
2. 而进程是程序运行在数据集上的动态过程,进程是一个动态实体,它应创建而产生,应调度执行因等待资
源或事件而被处于等待状态,因完成任务而被撤消
3. 进程是系统进行资源分配和调度的一个独立单位
4.一个程序对应多个进程,一个进程为多个程序服务(两者之间是多对多的关系)
5. 一个程序执行在不同的数据集上就成为不同的进程,可以用进程控制块来唯一地标识每个进程
多线程
Python多线程编程中常用方法:
1、join()方法:如果一个线程或者在函数执行的过程中调用另一个线程,并且希望待其完成操作后才能执行,
那么在调用线程的时就可以使用被调线程的join方法join([timeout]) timeout:可选参数,线程运行的最长时间
2、isAlive()方法:查看线程是否还在运行
3、getName()方法:获得线程名
4、setDaemon()方法:主线程退出时,需要子线程随主线程退出,则设置子线程的setDaemon()
1、线程2种调用方式:直接调用, 继承式调用
import threading import time def sayhi(num): # 定义每个线程要运行的函数 print("running on number:%s" % num) time.sleep(3) #1、target=sayhi :sayhi是定义的一个函数的名字 #2、args=(1,) : 括号内写的是函数的参数 t1 = threading.Thread(target=sayhi, args=(1,)) # 生成一个线程实例 t2 = threading.Thread(target=sayhi, args=(2,)) # 生成另一个线程实例 t1.start() # 启动线程 t2.start() # 启动另一个线程 print(t1.getName()) # 获取线程名 print(t2.getName()) 直接调用
import threading import time class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num = num def run(self):#定义每个线程要运行的函数 print("running on number:%s" %self.num) time.sleep(3) if __name__ == '__main__': t1 = MyThread(1) t2 = MyThread(2) t1.start() t2.start() 继承式调用
2、for循环同时启动多个线程
说明:下面利用for循环同时启动50个线程并行执行,执行时间是3秒而不是所有线程执行时间的总和
import threading import time def sayhi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3) for i in range(50): t = threading.Thread(target=sayhi,args=('t-%s'%i,)) t.start() for循环启动多个线程
3、t.join(): 实现所有线程都执行结束后再执行主线程
说明:在4中虽然可以实现50个线程同时并发执行,但是主线程不会等待子线程结束在这里我们可以使用t.join()指定等待某个线程结束的结果
import threading import time start_time = time.time() def sayhi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3) t_objs = [] #将进程实例对象存储在这个列表中 for i in range(50): t = threading.Thread(target=sayhi,args=('t-%s'%i,)) t.start() #启动一个线程,程序不会阻塞 t_objs.append(t) print(threading.active_count()) #打印当前活跃进程数量 for t in t_objs: #利用for循环等待上面50个进程全部结束 t.join() #阻塞某个程序 print(threading.current_thread()) #打印执行这个命令进程 print("----------------all threads has finished.....") print(threading.active_count()) print('cost time:',time.time() - start_time) t.join() 主线程等待子线程
4、setDaemon(): 守护线程,主线程退出时,需要子线程随主线程退出
import threading import time start_time = time.time() def sayhi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3) for i in range(50): t = threading.Thread(target=sayhi,args=('t-%s'%i,)) t.setDaemon(True) #把当前线程变成守护线程,必须在t.start()前设置 t.start() #启动一个线程,程序不会阻塞 print('cost time:',time.time() - start_time) 守护线程
注:因为刚刚创建的线程是守护线程,所以主线程结束后子线程就结束了,运行时间不是3秒而是0.01秒
5、GIL锁和用户锁(Global Interpreter Lock 全局解释器锁
)
1.全局解释器锁:保证同一时间仅有一个线程对资源有操作权限
作用:在一个进程内,同一时刻只能有一个线程通过GIL锁 被CUP调用,切换条件:I/O操作、固定时间(系统决定)
说明:python多线程中GIL锁只是在CPU操作时(如:计算)才是串行的,其他都是并行的,所以比串行快很多
1)为了解决不同线程同时访问同一资源时,数据保护问题,而产生了GIL
2)GIL在解释器的层面限制了程序在同一时间只有一个线程被CPU实际执行,而不管你的程序里实际开了多少条线程
3)为了解决这个问题,CPython自己定义了一个全局解释器锁,同一时间仅仅有一个线程可以拿到这个数据
4)python之所以会产生这种不好的状况是因为python启用一个线程是调用操作系统原生线程,就是C接口
5)但是这仅仅是CPython这个版本的问题,在PyPy,中就没有这种缺陷
2. 用户锁:线程锁(互斥锁Mutex) :当前线程还未操作完成前其他所有线程都无法对其操作,即使已经释放了GIL锁
1. 在有GIL锁时为何还需要用户锁
1)GIL锁只能保证同一时间只能有一个线程对某个资源操作,但当上一个线程还未执行完毕时可能就会释放GIL,其他线程就可以操作了
2. 线程锁的原理
1)当一个线程对某个资源进行CPU计算的操作时加一个线程锁,只有当前线程计算完成主动释放锁,其他线程才能对其操作
2)这样就可以防止还未计算完成,释放GIL锁后其他线程对这个资源操作导致混乱问题
import time import threading lock = threading.Lock() #1 生成全局锁 def addNum(): global num #2 在每个线程中都获取这个全局变量 print('--get num:',num ) time.sleep(1) lock.acquire() #3 修改数据前加锁 num -= 1 #4 对此公共变量进行-1操作 lock.release() #5 修改后释放 用户锁使用举例
3. 在有GIL的情况下执行 count = count + 1 会出错原因解析,用线程锁解决方法
# 1)第一步:count = 0 count初始值为0 # 2)第二步:线程1要执行对count加1的操作首先申请GIL全局解释器锁 # 3)第三步:调用操作系统原生线程在操作系统中执行 # 4)第四步:count加1还未执行完毕,时间到了被要求释放GIL # 5)第五步:线程1释放了GIL后线程2此时也要对count进行操作,此时线程1还未执行完,所以count还是0 # 6)第六步:线程2此时拿到count = 0后也要对count进行加1操作,假如线程2执行很快,一次就完成了 # count加1的操作,那么count此时就从0变成了1 # 7)第七步:线程2执行完加1后就赋值count=1并释放GIL # 8)第八步:线程2执行完后cpu又交给了线程1,线程1根据上下文继续执行count加1操作,先拿到GIL # 锁,完成加1操作,由于线程1先拿到的数据count=0,执行完加1后结果还是1 # 9)第九步:线程1将count=1在次赋值给count并释放GIL锁,此时连个线程都对数据加1,但是值最终是1 出错原因分析
1、使用线程锁解决上面问题的原理
1) 在GIL锁中再加一个线程锁,线程锁是用户层面的锁
2) 线程锁就是一个线程在对数据操作前加一把锁,防止其他线程复制或者操作这个数据
3) 只有这个线程对数据操作完毕后才会释放这个锁,其他线程才能操作这个数据
2、定义一个线程锁非常简单只用三步:
第一步: lock = threading.Lock() #定义一把锁
第二步: lock.acquire() #对数据操作前加锁防止数据被另一线程操作
第三步: lock.release() #对数据操作完成后释放锁
6、死锁
1. 死锁定义
两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
2. 死锁举例
1. 启动5个线程,执行run方法,假如thread1首先抢到了A锁,此时thread1没有释放A锁,紧接着执行代码mutexB.acquire(),抢到了B锁,
在抢B锁时候,没有其他线程与thread1争抢,因为A锁没有释放,其他线程只能等待
2. thread1执行完func1函数,然后执行func2函数,此时thread1拿到B锁,然后执行time.sleep(2),此时不会释放B锁
3. 在thread1执行func2的同时thread2开始执行func1获取到了A锁,然后继续要获取B锁
4. 不幸的是B锁还被thread1占用,thread1占用B锁时还需要同时获取A锁才能向下执行,但是此时发现A锁已经被thread2暂用,这样就死锁了
from threading import Thread,Lock import time mutexA=Lock() mutexB=Lock() class MyThread(Thread): def run(self): self.func1() self.func2() def func1(self): mutexA.acquire() print('