1、进程、与线程区别
2、python GIL全局解释器锁
3、线程
- 语法
- join
- 线程锁之LockRlock信号量
- 将线程变为守护进程
- Event事件
- queue队列
- 生产者消费者模型
- Queue队列
- 开发一个线程池
4、进程
- 语法
- 进程间通讯
- 进程池
一、进程与线程
什么是进程(process)?
程序的执行实例称为进程。
每个进程都提供执行程序所需的资源。 进程具有虚拟地址空间,可执行代码,系统对象的打开句柄,
安全上下文,唯一进程标识符,环境变量,优先级类,最小和最大工作集大小以及至少一个执行线程。
每个进程都使用单个线程启动,通常称为主线程,但可以从其任何线程创建其他线程。
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。
程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;
进程是程序的一次执行活动,属于动态概念。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。
这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,
因此,进程就是为了在CPU上实现多道编程而提出的。
有了进程为什么还要线程?
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。
很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,
主要体现在两点上:
-
进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
-
进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
例如, 一个操作系统就像是一个工厂,工厂里面有很多个生产车间,不同的车间生产不同的产品,
每个车间就相当于一个进程,且你的工厂又穷,供电不足,同一时间只能给一个车间供电,
为了能让所有车间都能同时生产,你的工厂的电工只能给不同的车间分时供电,但是轮到你的qq车间时,
发现只有一个干活的工人,结果生产效率极低,为了解决这个问题,应该怎么办呢?
就是多加几个工人,让几个人工人并行工作,这每个工人,就是线程!
什么是线程(thread)?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,
每条线程并行执行不同的任务
线程是执行上下文,它是CPU执行指令流所需的所有信息。
假设你正在读一本书,而你现在想休息一下,但是你希望能够从你停下来的确切位置回来并继续阅读。
实现这一目标的一种方法是记下页码,行号和字号。因此,阅读书籍的执行环境就是这三个数字。
如果你有一个室友,并且她使用相同的技术,她可以在你不使用时拿走这本书,并从她停下的地方继续阅读。
然后你可以把它拿回来,并从你原来的地方恢复。
线程以相同的方式工作。 CPU正在给你一种错觉,即它同时进行多次计算。它通过在每次计算上花费一点时间来做到这一点。
它可以这样做,因为它具有每个计算的执行上下文。就像您可以与朋友共享一本书一样,许多任务可以共享CPU。
在更技术层面上,执行上下文(因此是一个线程)由CPU寄存器的值组成。
最后:线程与进程不同。线程是执行的上下文,而进程是与计算相关联的一堆资源。一个进程可以有一个或多个线程。
澄清:与进程相关联的资源包括内存页面(进程中的所有线程具有相同的内存视图),
文件描述符(例如,打开套接字)和安全凭证(例如,启动该进程的用户的ID)处理)。
进程与线程的区别?
1、线程共享创建它的进程的地址空间; 进程有自己的地址空间。
2、线程可以直接访问其进程的数据段; 进程拥有自己父进程数据段的副本。
3、线程可以直接与其进程的其他线程通信; 进程必须使用进程间通信来与兄弟进程通信。
4、新线程很容易创建; 新流程需要复制父流程。
5、线程可以对同一进程的线程进行相当大的控制; 进程只能控制子进程。
6、对主线程的更改(取消,优先级更改等)可能会影响进程的其他线程的行为; 对父进程的更改不会影响子进程。
Python GIL(Global Interpreter Lock)
在CPython中,全局解释器锁(GIL)是一个互斥锁,它可以防止多个本机线程同时执行Python字节码。
这种锁是必要的,主要是因为CPython的内存管理不是线程安全的。
(但是,由于GIL存在,其他功能已经增长,取决于它强制执行的保证。)
简而言之,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行
GIL并不是Python的特性,Python完全可以不依赖于GIL
这篇文章透彻的剖析了GIL对python多线程的影响,强烈推荐看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf
Python threading模块
线程有2种调用方式,如下:
直接调用:
import threading import time def say_hi(num): #定义每个线程要运行的函数 print("running on number:%s" %num) time.sleep(3) if __name__ == '__main__': t1 = threading.Thread(target=say_hi,args=(1,)) #生成一个线程实例 t2 = threading.Thread(target=say_hi,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()
Join & Daemon
有些线程执行后台任务,例如发送keepalive数据包,或执行定期垃圾收集等等。
这些仅在主程序运行时才有用,并且一旦其他非守护程序线程退出就可以将它们终止。
如果没有守护程序线程,您必须跟踪它们,并在程序完全退出之前告诉它们退出。
通过将它们设置为守护程序线程,您可以让它们运行并忘记它们,
当程序退出时,任何守护程序线程都会自动终止。
注意:守护程序线程在关闭时突然停止。 他们的资源(例如打开文件,数据库事务等)可能无法正确发布。
如果您希望线程正常停止,请将它们设置为非守护进程并使用合适的信号机制(如Event)。
join
import threading import time def run(n): print("task",n) time.sleep(2) print('task done:',n,threading.current_thread()) obj_res = []#存线程实例 start_time = time.time() for i in range(50): t = threading.Thread(target=run,args=('t-%s'%i,)) obj_res.append(t)#为了不阻塞后面线程的启动,不在这里join t.start() for t in obj_res:#循环线程实例列表,等待所有线程执行完毕 t.join()#wait t的执行结果 stop_time = time.time() #程序本身就是主线程 print("--all threads has finish---",threading.current_thread(),threading.active_count()) print('total time:%s'%(stop_time-start_time))
守护线程
''' 守护线程,程序不会等守护线程执行完毕才结束程序。 程序会等主线程执行完就结束程序。 ''' import threading import time def run(n): print("task",n) time.sleep(2) start_time = time.time() for i in range(50): t = threading.Thread(target=run,args=('t-%s'%i,)) t.setDaemon(True)#把当前线程设置为守护线程 t.start() stop_time = time.time() print("--all threads has finish---") print('total time:%s'%(stop_time-start_time))
线程锁(互斥锁Mutex)
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,
也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
Python同一时间只允许一个线程访问,修改数据,很简单,
每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,
可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕
并把锁释放掉后才能再访问此数据。
1 import time,threading 2 3 lock = threading.Lock()#生成全局锁 4 5 6 7 def addnum(): 8 9 global num#在每个线程中都获取这个全局变量 10 print('---get num:',num) 11 time.sleep(1) 12 lock.acquire()#修改数据前加锁 13 num -= 1#对此公共变量进行-1操作 14 lock.release()#修改后释放 15 16 num =100 #设置一个共享变量 17 18 thread_list = [] 19 20 21 for i in range(100): 22 t = threading.Thread(target=addnum) 23 24 t.start() 25 26 thread_list.append(t) 27 28 for t in thread_list:#等待所有线程执行完毕 29 t.join() 30 31 print('final num:',num)
GIL VS Lock
机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,
为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 。
那你又问了, 既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?加入GIL主要的原因是为了降低程序的开发的复杂度,
比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,
你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,
此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,
py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,
结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,
即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是Python早期版本的遗留问题。
RLock(递归锁)
说白了就是在一个大锁中还要再包含子锁
import threading, time def run1(): print("grab the first part data") lock.acquire() global num num += 1 lock.release() return num def run2(): print("grab the second part data") lock.acquire() global num2 num2 += 1 lock.release() return num2 def run3(): lock.acquire() res = run1()#res为run1()函数的返回值,即num的值 print('--------between run1 and run2-----') res2 = run2()#res2为run2()函数的返回值,即num2的值 lock.release() print(res, res2) ''' 递归锁实现原理: 类似于以下方式 { lock1:key1, lock2:key2 } ''' num, num2 = 0, 0 lock = threading.RLock()#设置成递归锁,连续锁好几次必须要用递归锁 for i in range(10): t = threading.Thread(target=run3)#实例化线程 t.start()#启动线程 while threading.active_count() != 1:#检测当前程序运行中的线程数量 print('threading_count:',threading.active_count()) else: print('----all threads done---') print(num, num2)
Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,
比如在自主银行有3个ATM机,有5个人想取钱,那最多只允许3个人进去,后面的人只能等里面有人出来了才能再进去。
#Author:Yun import threading, time ''' 信号量与单个锁的区别是,信号量有多把锁 ''' def run(n): semaphore.acquire()#获取一把锁 time.sleep(1) print("run the thread: %s " % n) semaphore.release()#释放锁 if __name__ == '__main__': num = 0 semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行 for i in range(20): #循环生成20个线程 t = threading.Thread(target=run, args=(i,)) t.start() while threading.active_count() != 1: pass # print threading.active_count() else: print('----all threads done---') print(num)
Timer
此类表示仅在经过一定时间后才应运行的操作
与线程一样,通过调用start()方法启动计时器。 通过调用thecancel()方法可以停止计时器(在其动作开始之前)。
计时器在执行其操作之前将等待的时间间隔可能与用户指定的时间间隔不完全相同。
timer实例1
#Author:Yun
import time,threading
def hello():
print("