线程与进程
什么是线程?什么是进程
进程定义:
进程就是一个程序在一个数据集上的一次动态执行过程.
进程一般由程序,数据集,进程控制块三部分组成
我们编写的程序用来描述进程要完成哪些功能以及如何完成
数据集则是程序在执行过程中所需要使用的资源
进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它啦控制和管理进 程,他是系统感知进程存在的唯一标志
线程定义:
线程也叫轻量级进程, 他是一个基本的CPU执行单元, 也是程序执行过程中的最小单元,由线程ID, 程序,计数器,寄存器集合和堆栈共同组成.线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能.
线程没有自己的系统资源
线程与进程的关系?
1.一个程序至少有一个进程,一个进程至少有一个线程.(进程可以理解成线程的容器)
2.进程在执行过程中拥有独立内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率
3.线程在执行过程中与进程还是有区别的.每个独立的线程有一个程序运行的入口,孙旭执行序列和程序的出口.但是线程不能独立执行,必须依存在应用程序中,有应用程序提供多个线程执行控制
4.进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程 自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行
什么是GIL?
GIL即python中的全局变量锁,它维护着线程安全,但前提是限制了单个主线程中的子线程同时运行,无论你开启多少个线程,你有多少个CPU,python在执行的时候会限制在同一时刻只允许一个线程运行
线程的两种调用方式
第一种
1 import threading 2 import time 3 4 5 6 def music(): 7 print("start to listen %s"%time.ctime()) 8 time.sleep(3) 9 print("end to listen %s"%time.ctime()) 10 11 def game(): 12 print("start to playgame %s"%time.ctime()) 13 time.sleep(5) 14 print("end to playgame %s"%time.ctime()) 15 16 17 if __name__ == '__main__': 18 t1 = threading.Thread(target=music) #实例化一个子线程 19 t2 = threading.Thread(target=game) 20 21 t1.start() #执行子线程 22 t2.start() 23 24 t2.join() #等待子线程结束后执行后面代码 25 t1.join() 26 27 print("======ending=========") #主线程与子线程并发,如果存在join,则需等待子线程结束后方可执行 28 29 直接调用
第二种
1 import threading 2 import time 3 4 class MyThread(threading.Thread): 5 def __init__(self,num): 6 threading.Thread.__init__(self) 7 self.num = num 8 def run(self): #定义每个线程要运行的函数 9 print("running on number:%s" % self.num) 10 11 time.sleep(3) 12 13 if __name__ == '__main__': 14 t1 = MyThread(1) 15 t2 = MyThread(2) 16 t1.start() 17 t2.start() 18 19 print("ending......") 20 21 继承
join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞
setDaemon(True):
将线程声明为守护线程,必须在start()方法调用之前设置,如果不设置为守护线,程序会被无限挂起.这个方法基本和join是相反的
当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程就分兵两路,分别运行,那么当主线程完成,想退出时,会校验子线程是否完成.如果子线程未完成,则主线程会等子线程完成后再退出.但有时后我们需要的只是主线程
完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon方法了
1 import threading 2 from time import ctime,sleep 3 import time 4 5 def ListenMusic(name): 6 7 print ("Begin listening to %s. %s" %(name,ctime())) 8 sleep(3) 9 print("end listening %s"%ctime()) 10 11 def RecordBlog(title): 12 13 print ("Begin recording the %s! %s" %(title,ctime())) 14 sleep(5) 15 print('end recording %s'%ctime()) 16 17 18 threads = [] 19 20 21 t1 = threading.Thread(target=ListenMusic,args=('水手',)) 22 t2 = threading.Thread(target=RecordBlog,args=('python线程',)) 23 24 threads.append(t1) 25 threads.append(t2) 26 27 if __name__ == '__main__': 28 29 for t in threads: 30 #t.setDaemon(True) #注意:一定在start之前设置 31 t.start() 32 # t.join() 33 # t1.join() 34 t1.setDaemon(True) 35 36 #t2.join()########考虑这三种join位置下的结果? 37 print ("all over %s" %ctime())
1 import threading 2 from time import ctime,sleep 3 import time 4 5 def ListenMusic(name): 6 7 print ("Begin listening to %s. %s" %(name,ctime())) 8 sleep(3) 9 print("end listening %s"%ctime()) 10 11 def RecordBlog(title): 12 13 print ("Begin recording the %s! %s" %(title,ctime())) 14 sleep(5) 15 print('end recording %s'%ctime()) 16 17 18 threads = [] 19 20 21 t1 = threading.Thread(target=ListenMusic,args=('水手',)) 22 t2 = threading.Thread(target=RecordBlog,args=('python线程',)) 23 24 threads.append(t1) 25 threads.append(t2) 26 27 if __name__ == '__main__': 28 29 for t in threads: 30 #t.setDaemon(True) #注意:一定在start之前设置 31 t.start() 32 # t.join() 33 # t1.join() 34 t1.setDaemon(True) 35 36 #t2.join()########考虑这三种join位置下的结果? 37 print ("all over %s" %ctime())
其他方法:
# run(): 线程被cpu调度后自动执行线程对象的run方法
# start():启动线程活动。 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。
threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
互斥锁
1 import threading 2 import time 3 4 5 6 def sub(): 7 global num 8 #num -= 1 #直接-1重新赋值给num,之间处理速度相当快,还没等到CPU时间轮循,该线程已处理结束, 9 lock.acquire() #用同步锁可将下列过程绑定在一起,即等待时间CPU也不会切换 10 temp = num 11 time.sleep(0.000000000000000000000000001) 12 num = temp - 1 13 lock.release() 14 num = 100 15 lock = threading.Lock() #生成一个同步锁变量 16 l = [] 17 for i in range(100): 18 t = threading.Thread(target=sub) 19 t.start() 20 l.append(t) 21 for m in l: 22 m.join() 23 24 print(num)
由于多个线程都在操作同一个资源,同步锁可保证资源不被破坏。
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资 源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态 为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线 程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情 况下数据的正确性。
线程死锁和递归锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都在使用,所以这两个线程在外力作用下将一直等待下去
1 死锁 2 import threading 3 import time 4 5 6 class Mythread(threading.Thread): 7 def actionA(self): 8 r_lock.acquire() 9 print(self.name, "gotA", time.ctime()) 10 time.sleep(2) 11 r_lock.acquire() 12 13 print(self.name, "gotB", time.ctime()) 14 time.sleep(1) 15 r_lock.release() 16 r_lock.release() 17 18 def actionB(self): 19 r_lock.acquire() 20 print(self.name, "gotB", time.ctime()) 21 time.sleep(2) 22 r_lock.acquire() 23 24 print(self.name, "gotA", time.ctime()) 25 time.sleep(1) 26 A.release() 27 B.release() 28 29 def run(self): 30 self.actionA() 31 self.actionB()
1 #递归锁 2 import threading 3 import time 4 5 6 class Mythread(threading.Thread): 7 def actionA(self): 8 r_lock.acquire() 9 print(self.name, "gotA", time.ctime()) 10 time.sleep(2) 11 r_lock.acquire() 12 13 print(self.name, "gotB", time.ctime()) 14 time.sleep(1) 15 r_lock.release() 16 r_lock.release() 17 18 def actionB(self): 19 r_lock.acquire() 20 print(self.name, "gotB", time.ctime()) 21 time.sleep(2) 22 r_lock.acquire() 23 24 print(self.name, "gotA", time.ctime()) 25 time.sleep(1) 26 r_lock.release() 27 r_lock.release() 28 29 def run(self): 30 self.actionA() 31 self.actionB() 32 33 34 if __name__ == '__main__': 35 r_lock = threading.RLock() #递归锁,内部有一个count计数器,每加一次锁count+1,每减一次count-1,当count>0时,其他线程无法获得 36 l = [] 37 for i in range(5): 38 t = Mythread() 39 t.start() 40 l.append(t) 41 for v in l: 42 v.join()
为了支持在同一线程中多次请求同一资源,python提供了"可重入锁":
threading.RLock。RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的 次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线 程才能获得资源.
同步条件
event.wait():等待同步
event.set():设置同步
event.clear():清除同步
1 import threading,time 2 class Boss(threading.Thread): 3 def run(self): 4 print("BOSS:今晚大家都要加班到22:00。") 5 print(event.isSet()) 6 event.set() 7 time.sleep(5) 8 print("BOSS:<22:00>可以下班了。") 9 print(event.isSet()) 10 event.set() 11 class Worker(threading.Thread): 12 def run(self): 13 event.wait() 14 print("Worker:哎……命苦啊!") 15 time.sleep(1) 16 event.clear() 17 event.wait() 18 print("Worker:OhYeah!") 19 if __name__=="__main__": 20 event=threading.Event() 21 threads=[] 22 for i in range(5): 23 threads.append(Worker()) 24 threads.append(Boss()) 25 for t in threads: 26 t.start() 27 for t in threads: 28 t.join()
信号量
信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1。
计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)
BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。
1 import threading,time 2 class myThread(threading.Thread): 3 def run(self): 4 if semaphore.acquire(): 5 print(self.name) 6 time.sleep(5) 7 semaphore.release() 8 if __name__=="__main__": 9 semaphore=threading.Semaphore(5) #参数表示容量个数 10 thrs=[] 11 for i in range(100): 12 thrs.append(myThread()) 13 for t in thrs: 14 t.start()
队列
创建一个"队列"对象
import Queue
q = Queue.Queue(maxsize=10)
Queue.Queue类即是一个队列的同步实现.队列长度可为无限或者有限.可通过Queue的构造函数的可选参数maxsize来设定队列长度.如果maxsize小于就表示队列长度无限
将一个值放入队列中
q.put(10)
调用队列对象的put()方法在队尾插入一个项目.put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为1.
如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元.如果block为0,put方法将引发Full异常
将一个值从队列中取出
q.get()
调用队列对象的get()方法从队头删除并返回一个项目.可选参数为block,默认为True.如果队列为空且block为True,
get()就使调用线程暂停,直至又项目可用.如果队列为空且block为Flase,队列引发Empty异常
python Queue模块有三种队列及构造函数:
1,python Queue模块的FIFO队列先进先出. class queue.Queue(maxsize)
2,LIFO类似于堆,即先进后出. class queue.LifoQueue(maxsize)
3,还有一种是优先级别越低越先出来 cass queue.PriorityQueue(maxsize)
此包中的常用方法(q = Queue.Queue()): q.qsize() 返回队列的大小 q.empty() 如果队列为空,返回True,反之False q.full() 如果队列满了,返回True,反之False q.full 与 maxsize 大小对应 q.get([block[, timeout]]) 获取队列,timeout等待时间 q.get_nowait() 相当q.get(False) 非阻塞 q.put(item) 写入队列,timeout等待时间 q.put_nowait(item) 相当q.put(item, False) q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号 q.join() 实际上意味着等到队列为空,再执行别的操作
1 import queue #线程队列 2 3 q = queue.Queue(3) #生成先进先出队列,参数代表队列限定宽度 4 # q = queue.LifoQueue #生成先进后出队列 5 # q = queue.PriorityQueue #生成优先级的队列 6 q.put("hello") 7 q.put(123) 8 q.put("world") 9 q.put_nowait(44) # 相当于q.put(block=False) 10 #q.put(5,block=False) #当队列满会报错 11 print(q.qsize()) #获取实际列表宽度 12 print(q.empty()) #判断列表是否为空 13 print(q.full()) #判断列表是否满 14 while 1: 15 t = q.get() #当列表为空时,会一直处于获取状态 16 #t = q.get_nowait() #相当于 q.get(block=False) 17 #t = q.get(block=False) #当列表为空时会报错 18 print(t)
线程和进程和协程的区别
首先我们来说一下进程、线程还有协程它们三个的区别?
进程作为操作系统资源分配的最小单位,可以在进程和进程之间进行数据隔离,如果有特殊需要通过Manager进行进程间的数据共享,而线程才是执行程序,与CPU进行交互的最小单位,一个CPU同时只能与一个线程进行交互,所以CPU个数与线程数相等才能发挥多线程最大的优势,但是遗憾的时Python中以为有GIL锁的存在,并不能这么做,GIL锁的存在限制了在同一时间一个进程中只能有一个线程与CPU进行交互,但是这并不影响Python多线程进行IO操作的效率,因为IO操作并不占用CPU,如果是计算密集型,Python就没有办法了,只能是用多进程来提升程序的效率了.再说协程,协程在真正意义上并不是真实存在的,通过gevent框架利来实现协程,主要是为了解决阻塞问题,可以发挥线程的最大效率,不必让线程发出请求后傻傻的等,原理是让对一个线程进行分片,在执行是遇到IO操作就直接调用另一个模块greenlet的switch方法来切换代码块.实现高效率的多线程,不只有gevent可以实现协程,还有Twisted,这两个的底层原理都是基于时间循环实现的异步非阻塞框架.