python--------进程与线程
引子:为什么要有操作系统
1,操作系统位于底层硬件与应用程序之间的一层
2,工作方式:向下管理硬件,向上提供接口
一,什么是进程
进程就是一个程序在一个个数据集上的一次动态执行过程。进程一般由程序,数据集,进程控制块三部分组成。
程序:用来描述进程要完成哪些功能以及如何完成。
数据集:是程序在执行过程中所需要使用的资源
进程控制块:用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用他来控制和管理进程,他是系统感知进程存在的唯一标志。
举例:
想象一位有一手好厨艺的计算机科学家正在为他的女儿烘制生日蛋糕。他有做生日蛋糕的食谱,厨房里有所需的原料:面粉、鸡蛋、糖、香草汁等。在这个比喻中,做蛋糕的食谱就是程序(即用适当形式描述的算法)计算机科学家就是处理器(cpu),而做蛋糕的各种原料就是输入数据。进程就是厨师阅读食谱、取来各种原料以及烘制蛋糕等一系列动作的总和。现在假设计算机科学家的儿子哭着跑了进来,说他的头被一只蜜蜂蛰了。计算机科学家就记录下他照着食谱做到哪儿了(保存进程的当前状态),然后拿出一本急救手册,按照其中的指示处理蛰伤。这里,我们看到处理机从一个进程(做蛋糕)切换到另一个高优先级的进程(实施医疗救治),每个进程拥有各自的程序(食谱和急救手册)。当蜜蜂蛰伤处理完之后,这位计算机科学家又回来做蛋糕,从他
离开时的那一步继续做下去。
二,什么是线程
线程的出现时为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干益阳市的缺陷,使到进程内并发称为可能。
假设,一个文本程序,需要接受键盘输入,将内容显示在屏幕上,还需要保存信息到硬盘中。若只有一个进程,势必造成同一时间只能干一样事的尴尬(当保存时,就不能通过键盘输入内容)。若有多个进程,每个进程负责一个任务,进程A负责接收键盘输入的任务,进程B负责将内容显示在屏幕上的任务,进程C负责保存内容到硬盘中的任务。这里进程A,B,C间的协作涉及到了进程通信问题,而且有共同都需要拥有的东西——-文本内容,不停的切换造成性能上的损失。若有一种机制,可以使任务A,B,C共享资源,这样上下文切换所需要保存和恢复的内容就少了,同时又可以减少通信所带来的性能损耗,那就好了。是的,这种机制就是线程。
线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。
进程:最小的资源管理单位,可以理解成容器,而线程是容器里的东西
线程:最小的可执行单位,真正执行的是线程
三,进程与线程的关系
1,一个线程只能属于一个进程,而一个进程可以有多个线程,注意:创建一个进程,最少必须有一个线程。
2,资源分配给进程,同一进程的所有线程共享该进程的所有资源
3,CPU分给线程,真正在CPU上运行的是线程。
四,并行和并发
并行处理(Parallel Processing):
是计算机系统中能同时执行两个或更多个处理的一种计算方法。并行处理可同时工作于同一程序的不同方面。并行处理的主要目的是节省大型和复杂问题的解决时间。
一个进程:同一时间多个线程同时执行,基于有几个cpu,就是并行
并发处理(concurrency Processing):
指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机(cpu)上运行,但是,只能任一个时刻上只有一个程序在处理机(cpu)上运行
一个进程:多个线程轮流切换执行,就是并发
串行
从上到下一个一个执行,
五,同步与异步
在计算机领域,同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。
举个例子,打电话时就是同步通信,发短息时就是异步通信。
在python中,多线程由于GIL锁,导致同一时刻同一线程只能有一个被执行,所以没有并行,但是可用多进程来实现并行的效果
六,python并发之多线程
threading模块:
python中把所有与线程相关的操作,都封装成了threading接口,所有的操作都是通过threading模块来实现的
调用方式1:
1 #调用方式1
2 import threading
3 import time
4
5 def look():
6 print("看dog") # 1
7 time.sleep(3) # t1遇到IO睡3秒 线程切换到t2
8 print("看dog结束") # 3
9 def walk():
10 print("开始walking") #2
11 time.sleep(5) # t2遇到IO睡五秒 线程切换到t1
12 print("walking结束") #4
13 print("程序运行时间:%s"%(time.time()-s)) #5
14 s=time.time()
15 t1 = threading.Thread(target=look) #创建t1对象
16 t2 = threading.Thread(target=walk) #创建t2对象
17 t1.start()
18 t2.start()
调用方式2:继承threading类
import threading
import time
#调用方式2 ,继承threading类
class MyThread(threading.Thread): #继承threading模块下的Thread类
def __init__(self,num):
threading.Thread.__init__(self) #继承父类的init方法
self.num = num
def run(self): #重写父类的run方法
print("running on number:%s"%self.num)
time.sleep(3)
print("结束")
t1 = MyThread(100)
t2 = MyThread(200)
t1.start()
print("ending")
t2.start()
#执行结果
running on number:100
ending
running on number:200
结束
结束
Thread实例对象的方法
# isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
jion与setdamemon方法:
jion:一个线程对象未执行完毕,会阻塞你的主线程
setdamenmon:守护线程:程序直到不存在非守护线程是退出
# join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
# setDaemon(True):
'''
将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。
当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成
想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是只要主线程
完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦'''
import threading
from time import ctime,sleep
import time
def Music(name):
print ("Begin listening to {name}. {time}".format(name=name,time=ctime()))
sleep(3)
print("end listening {time}".format(time=ctime()))
def Blog(title):
print ("Begin recording the {title}. {time}".format(title=title,time=ctime()))
sleep(5)
print('end recording {time}'.format(time=ctime()))
threads = []
t1 = threading.Thread(target=Music,args=('FILL ME',))
t2 = threading.Thread(target=Blog,args=('',))
threads.append(t1)
threads.append(t2)
if __name__ == '__main__':
#t2.setDaemon(True)
for t in threads:
#t.setDaemon(True) #注意:一定在start之前设置
t.start()
#t.join()
#t1.join()
#t2.join() # 考虑这三种join位置下的结果?
print ("all over %s" %ctime())
七,UIL全局解释器锁
GIL全局锁,只有cpython中有,别的解释器没有
计算密集型:一直在使用cup,计算密集型任务,python多线程并没有用
IO密集型:存在大量的IO操作,对于IO密集型任务,python 多线程是有意义的
八,同步锁(lock)
锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:
import time,threading
def subnum():
global num #全局变量
#num-=1
#print("ok")
#lock.acquire() #锁定开始,获取
temp = num
time.sleep(0.0000001)
num = temp - 1
#lock.release() #锁定结束,释放
num = 100
thread_list = []
lock = threading.Lock() #创建一把锁
for i in range(100):
t = threading.Thread(target=subnum)
t.start()
thread_list.append(t)
for l in thread_list:
l.join()
print("result:",num)
九,死锁与递归锁
在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:
import threading
import time
mutexA = threading.Lock()
mutexB = threading.Lock()
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
self.fun1()
self.fun2()
def fun1(self):
mutexA.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放
print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
mutexB.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
mutexB.release()
mutexA.release()
def fun2(self):
mutexB.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
time.sleep(0.2)
mutexA.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
mutexA.release()
mutexB.release()
if __name__ == "__main__":
print("start---------------------------%s"%time.time())
for i in range(0, 10):
my_thread = MyThread()
my_thread.start()
Rlock:
import threading
import time
# mutexA = threading.Lock()
# mutexB = threading.Lock()
rlock = threading._RLock()
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
self.fun1()
self.fun2()
def fun1(self):
#mutexA.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放
rlock.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
#mutexB.acquire()
rlock.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
#mutexB.release()
rlock.release()
#mutexA.release()
rlock.release()
def fun2(self):
#mutexB.acquire()
rlock.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
time.sleep(0.2)
# mutexA.acquire()
rlock.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
#mutexA.release()
rlock.release()
#mutexB.release()
rlock.release()
if __name__ == "__main__":
print("start---------------------------%s"%time.time())
for i in range(0, 10):
my_thread = MyThread()
my_thread.start()
十,event对象
event对象:默认False
用于两个线程通信的场景
event.wait 判断flag是否为False
event.set 把Flase改为True
线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就 会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行
event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。
十一,队列(queue模块)
get与put方法:
'''
创建一个“队列”对象
import Queue
q = Queue.Queue(maxsize = 10)
Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数
maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。
将一个值放入队列中
q.put(10)
调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;
第二个block为可选参数,默认为
1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,
put方法将引发Full异常。
将一个值从队列中取出
q.get()
调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且
block为True,get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。
'''
join与task_done方法:
'''
join() 阻塞进程,直到所有任务完成,需要配合另一个方法task_done。
def join(self):
with self.all_tasks_done:
while self.unfinished_tasks:
self.all_tasks_done.wait()
task_done() 表示某个任务完成。每一条get语句后需要一条task_done。
import queue
q = queue.Queue(5)
q.put(10)
q.put(20)
print(q.get())
q.task_done()
print(q.get())
q.task_done()
q.join()
print("ending!")
'''
先进先出,先进后出,优先级模式:
'''
Python Queue模块有三种队列及构造函数:
1、Python Queue模块的FIFO队列先进先出。 class queue.Queue(maxsize)
2、LIFO类似于堆,即先进后出。 class queue.LifoQueue(maxsize)
3、还有一种是优先级队列级别越低越先出来。 class queue.PriorityQueue(maxsize)
import queue
#先进后出
q=queue.LifoQueue()
q.put(34)
q.put(56)
q.put(12)
#优先级
q=queue.PriorityQueue()
q.put([5,100])
q.put([7,200])
q.put([3,"hello"])
q.put([4,{"name":"alex"}])
while 1:
data=q.get()
print(data)
'''
生产者消费者模型
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去饭菜也不需要不找厨师,直接去前台领取即可,这也是一个结耦的过程。
import time,random
import queue,threading
q = queue.Queue()
def Producer(name):
count = 0
while count <10:
print("making........")
time.sleep(random.randrange(3))
q.put(count)
print('Producer %s has produced %s baozi..' %(name, count))
count +=1
#q.task_done()
#q.join()
print("ok......")
def Consumer(name):
count = 0
while count <10:
time.sleep(random.randrange(4))
if not q.empty():
data = q.get()
#q.task_done()
#q.join()
print(data)
print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data))
else:
print("-----no baozi anymore----")
count +=1
p1 = threading.Thread(target=Producer, args=('A',))
c1 = threading.Thread(target=Consumer, args=('B',))
# c2 = threading.Thread(target=Consumer, args=('C',))
# c3 = threading.Thread(target=Consumer, args=('D',))
p1.start()
c1.start()
# c2.start()
# c3.start()