python 进程/线程详解
进程定义:以一个整体的形式暴露给操作系统管理,它里面包含对各种资源的调用,内存的管理,网络接口的调用等等,对各种资源管理的集合,就可以叫做一个进程。
线程定义:线程是操作系统能够进行运算调度的最小单位(是一串指令的集合)。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
另说明:进程是不能直接操作CPU执行的,每个进程的执行都是默认创建一个主线程来操作CPU进行执行指令集合。
进程和线程的区别:
1:同一进程内的多个线程之间是共享内存空间,每个进程的内存是独立的。
2:同一个进程内的线程之间可以直接交流,而俩个进程如果想通讯,必须通过一个中间代理来实现,如(进程队列,管道,manage)。
3:创建新线程很简单,创建新进程需要对其父进程进行一次克隆。
4:一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程.
举例一个最简单的多线程实现并发效果:
1 import threading,time 2 def fun(n): # 定义一个函数,每次打印后等待2秒钟退出 3 print(n) 4 time.sleep(2) 5 for i in range(5): 6 t = 't%d' % i 7 # 通过线程并发执行5次 8 t = threading.Thread(target=fun,args=('jack',)) 9 t.start()
举例:一个继承式多线程写法(只是一种方法而已)
1 import threading,time 2 class MyThread(threading.Thread): 3 def __init__(self,n): 4 # 重写父类中的属性值 5 super(MyThread,self).__init__() 6 self.n = n 7 def run(self): # 这里的run为固定模式,就是重写父类中的run方法 8 print('输出的字符串为:',self.n) 9 time.sleep(2) 10 for i in range(50): # 一次执行了50个线程 11 n = 'n'+str(i) 12 t = MyThread(n) 13 t.start()
举例:判断每个线程执行的时间
1 class MyThread(threading.Thread): 2 '''继承线程类并重写run方法''' 3 def __init__(self,n): 4 super(MyThread,self).__init__() 5 self.n = n 6 # 函数为启动的子线程执行的 7 def run(self): 8 print('输出的字符串为:',self.n) 9 time.sleep(2) 10 # 以下为主线程的执行 11 obj_list = [] 12 for i in range(50): 13 n = 'n'+str(i) 14 t = MyThread('t1') 15 t.start() 16 # 将每个线程内存添加到列表中 17 obj_list.append(t) 18 new_time = time.time() 19 for j in obj_list: 20 # 等待每个线程执行完成后,计算线程的用时 21 j.join() 22 out_time = time.time() - new_time 23 print('用时:',out_time) 24 print('所有线程一共用时:%s' % (time.time()-new_time))
守护线程:主线程退出时不会等待守护线程是否执行完毕,只会等待非守护线程执行完才退出。
使用情况:一般用于socketserver端,启动多个守护线程,当主线程关闭了,子线程就自动关闭,否则关闭主线程需等待子线程关闭了才可以关闭。
举例:将子线程修改为守护线程
1 class MyThread(threading.Thread): 2 def __init__(self,n): 3 super(MyThread,self).__init__() 4 self.n = n 5 def run(self): 6 print('输出的字符串为:',self.n) 7 time.sleep(2) 8 new_time = time.time() 9 for i in range(50): 10 n = 'n'+str(i) 11 t = MyThread('t1') 12 # 如下将线程t 通过setDaemon即声明为一个守护线程 13 t.setDaemon(True) 14 t.start() 15 print('所有线程一共用时:%s' % (time.time()-new_time))
GIL (全局解释器锁)
对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
1. 设置GIL
2. 切换到一个线程去运行
3. 运行:
a. 指定数量的字节码指令,或者
b. 线程主动让出控制(可以调用time.sleep(0))
4. 把线程设置为睡眠状态
5. 解锁GIL
6. 再次重复以上所有步骤
举例:线程手工加锁方法
1 import threading 2 lock = threading.Lock() # 实例化一个锁 3 num = 0 4 def fun(): 5 lock.acquire() #加锁 6 global num 7 num += 1 8 lock.release() #解锁 这样就在并发线程中将多个线程进行串行,也实现了对同一块内存进行操作 9 for i in range(50): 10 n = threading.Thread(target=fun,args=()) 11 n.start() 12 print('num:',num) 13 14 输出:num: 50
递归锁:一个外部锁内还有个子锁。(就是锁中有锁)
当程序中多个地方用到锁的时候,要使用递归锁: RLock()互斥锁(Mutex):一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,
如果2个线程同时修改同一份数据就会出现问题,这时候就要使用互斥锁以保证数据在操作时候是加锁的其他线程无法修改。
信号量(Semaphore)
互斥锁同时只允许一个线程更改数据,而信号量是同时允许一定数量的线程更改数据。
功能:就是用来控制同时运行的线程数量
比如;我们启动100个线程,通过信号量可以控制同一时刻有几个线程运行。
举例:
1 import threading 2 def fun(n): 3 semaph.acquire() #加锁 4 print(n) 5 time.sleep(2) 6 semaph.release() #解锁 7 8 semaph = threading.BoundedSemaphore(5) # 定义同时有5个线程运行 9 for i in range(23): 10 t = threading.Thread(target=fun,args=(i,)) 11 t.start()
events 事件
通过设置的标志位实现2个或多个线程间的交互
event = threading.Event()
event.wait() 等待设定标志位
event.set() 设定标志位
event.clear() 清空标志位
event.is_set() 判断是否设定标志位
举例:红绿灯
1 import threading 2 event = threading.Event() 3 4 def light(): # 定义一个红绿灯(也就是一个规则) 5 count = 0 # 默认0秒 6 event.set() # 首先设置开始默认标志位(绿灯) 7 while True: 8 if count > 5 and count < 10: 9 print('