python-线程、线程锁
线程:
线程是操作系统能够进行运算调度的最小单位,它被包含在进程中,是进程中的实际运作单位
一个进程实际上可以由多个线程的执行单元组成。每个线程都运行在进程的上下文中,并共享同样的代码和全局数据。
由于在实际的网络服务器中对并行的需求,线程成为越来越重要的编程模型,因为多线程之间比多进程之间更容易共享数据,同时线程一般比进程更高效
线程可以理解成轻量的进程,实际在linux两者几乎没有区别,唯一的区别是线程并不产生新的地址空间和资源.
创建多线程有两种方式:
1、函数调用多线程
import threading import time def sayhi(num): # 定义每个线程要运行的函数 print("running on number:%s" % num) time.sleep(3) def T_fun(): t1 = threading.Thread(target=sayhi, args=(1,)) # 生成一个线程实例 t2 = threading.Thread(target=sayhi, args=(2,)) # 生成另一个线程实例 t1.start() # 启动线程 t2.start() # 启动另一个线程 print('t1 name =',t1.getName()) # 获取线程名 print('t2 name =',t2.getName()) if __name__ == '__main__': T_fun()
>>>
running on number:1
running on number:2
t1 name = Thread-1
t2 name = Thread-2
2、继承调用多线程
import threading,time class My_Threading(threading.Thread):
def __init__(self,num): super(My_Threading, self).__init__() self.num = num self.second = 0
def run(self):#定义每个线程所要执行的任务 print('start - %d - '%self.num) time.sleep(3) for i in range(5): t = My_Threading(i) t.start()
>>>
start - 0 - start - 1 - start - 2 - start - 3 - start - 4 -
守护线程:
当我们调用多线程进行操作时,主线程会等待子线程全部运行完成后再结束,如果想不管子线程是否执行完任务,如果主线程执行完成就结束任务的话,就需要到了守护线程。
import threading,time def fun(num): time.sleep(1) print('num is ',num) for i in range(5): t = threading.Thread(target=fun,args =(i,)) t.setDaemon(True) #一定要在start之前设置 t.start() print('run the end') >>> run the end
#当主线程退出时,线程也会退出,由启动的其它子线程会同时退出,不管是否执行完任务
线程锁:
在我们进行多线程任务时,需要对同一个数据进行更改,但是我们需要这些线程一个一个的对其进行更改,我们就需要用到了线程锁。
'''没用线程锁''' def fun(): global num num += 1 # 完全计算密集型 for i in range(500): t = threading.Thread(target=fun) t.start() print('num =',num) >>>num = 500 def fun(): global num #1 temp = num #2 time.sleep(0.001) #I/O操作 #3 num = temp+1 #4 for i in range(500): t = threading.Thread(target=fun) t.start() print('num =',num) >>>num = 32 >>>num = 31 # 执行过程: ''' 在同一进程内,开启了500个线程,但因为GIL锁的原因(同一进程内,只有一个线程才可以被CPU调用(计算)) 1、当第一个线程(线程1)通过竞争获取到CPU执行权限后,会串行执行fun函数的 #1-#2,线程内的num的值为0 2、当到达第 #3 的时候,是I/O操作,所以开始切换到第二个个线程上执行同样的 #1-#2的操作,这个线程内的bun值也为0, 3、就这样碰到IO操作的时候就切换.... 4、但是当时间过了0.001秒的时候,[线程1]的线程I/O操作结束,执行 #4 将num的值更改为1 5、线程结束,切换到其他线程,此时的num值为1,此时[线程2]的I/O操作也结束了,进行num更改(此时线程2内的值为0),更改后的值为还是1 6、继续切换线程重新对num赋值,因为会发生重复操作,达不到我们想要的结果,所以就需要用到线程锁来保护我们的num '''
在这里,通过线程锁来总结一下Python在解释器中放置的GIL锁的问题,先不管为什么放置GIL锁,这里只说一下放置GIL锁的后果是:
one Python run, while N others sleep or await I/O.
在一个进程内,同一时刻只能有一个线程通过GIL锁 被CUP调用,切换条件:I/O操作、固定时间(系统决定)
而且在python2里,启用多线程进行密集运算的时间要比串行执行运算的事件要长,这一点在python3中被优化了很多,几乎和串行执行的时间相同。
''''使用线程锁之后''' import threading lock = threading.RLock() num = 0 #共享变量
def fun(): lock.acquire() global num num += 1 time.sleep(1) print('num =',num) lock.release() for i in range(5): t = threading.Thread(target=fun) t.start() >>> num = 1 num = 2 num = 3 num = 4 num = 5
Threading.Event:
Event 是线程之间通信的机制之一,一个线程负责发送一个event 信号,其他线程则等待这个信号,用于主线程控制其他线程的执行。
Event 主要有四个方法:
set() :设置标识位为 True
clear() : 设置表示位为 False wait([timeout]): 起到阻塞作用,参数为阻塞时间,如设置为True则继续运行
isSet(): 判断标识位是否设置为 True
import threading def do_Something(event_Obj): print('start:') event_Obj.wait() #这里起到阻塞的作用设置标识为等待设置为True时 才继续执行,如果添加参数则是以float为参数,表示阻塞XX秒后继续执行程序 print('execute!') event_Obj = threading.Event() #Event 在初始化时,标识位为False for i in range(5): t = threading.Thread(target=do_Something,args=(event_Obj,)) t.start() inp = input('>>>>') if inp == 'T': event_Obj.set() >>>
start:
start:
start:
start:
start:
>>>>T
execute!
execute!
execute!
execute!
execute!