python基础学习20----线程
什么是线程
线程,有时被称为轻量进程(lightweight process,lwp),是程序执行流的最小单元。一个标准的线程由线程id,当前指令指针(pc),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。进程内有一个相对独立的、可调度的执行单元,是系统独立调度和分派cpu的基本单位指令运行时的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
什么是进程
进程(process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
线程与进程的关系
线程和进程的关系:线程是属于进程的,线程运行在进程空间内,同一进程所产生的的线程共享同一用户内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。所以,线程不能独立地执行,它必须依附在一个运行的应用程序上(即进程上),而一个进程至少需要一个线程作为它的指令执行,进程管理着资源(比如cpu、内存、文件等等)。而将线程分配到某个cpu上执行。
python中的线程,threading模块
1.建立线程
import threading def func(msg): print(msg) print("这是一个线程") t=threading.thread(target=func,args=("hello world",)) t.start()
通过继承类的方式创建线程
class mythread(threading.thread): def __init__(self,name,age): threading.thread.__init__(self) self.name=name self.age=age def run(self): #这里是将threading.thread中的run方法进行了重载 print("%s is %d"%(self.name,self.age)) t=mythread("sfencs",19) t.start()
2.线程的并发
单个线程的创建基本没有意义,只是与主线程并发,现在我们看一下多个线程的并发
import threading import time class mythread(threading.thread): def __init__(self,name,age,second): threading.thread.__init__(self) self.name=name self.second=second self.age=age def run(self): print(self.name) time.sleep(self.second) print(self.age) t1=mythread("sfencs",19,2) t2=mythread("tom",25,5) t1.start() t2.start()
这里先同时打印sfencs和tom,过了两秒打印19,又过3秒打印25.这说明这两个线程是并发的,如果是串行的那么会使用7秒完成
我们可以使用time模块计算时间
import threading import time class mythread(threading.thread): def __init__(self,name,age,second): threading.thread.__init__(self) self.name=name self.second=second self.age=age def run(self): print(self.name) time.sleep(self.second) print(self.age) time_begin=time.time() t1=mythread("sfencs",19,2) t2=mythread("tom",25,5) t1.start() t2.start() time_end=time.time() print(time_end-time_begin)'''
sfencs
tom
0.0010306835174560547
19
25
'''
这里出现一个问题,输出的时间是0.0010306835174560547,而且在年龄之前输出的
原因是计算时间的代码属于主线程,它与两个自己创建的线程并发,所以它提前完成了计算,为了解决这个办法,我们使用join()方法
3.join()
一个线程使用join()方法后,必须等该线程结束后才执行join()之后的代码
time_begin=time.time() t1=mythread("sfencs",19,2) t2=mythread("tom",25,5) t1.start() t2.start() t1.join() t2.join() time_end=time.time() print(time_end-time_begin) ''' sfencs tom 19 25 5.001618146896362 '''
这样就显然的看出程序并发节约了约2秒钟
除此之外join()方法还有一个参数为阻塞的时间,默认为一直阻塞
4.io密集型任务和计算密集型任务
io密集型任务就如上述的例子一样,有阻塞的状态,如sleep()或者等待相关信息,信号时会停用cpu的任务。io密集型的任务在python中使用多线程能够很好的节约时间完成并发。
计算密集型任务没有等待状态,从上到下执行,没有任何等待
一个线程
import threading import time class mythread(threading.thread): def __init__(self,): threading.thread.__init__(self) def run(self): i=0 while i<100000000: i+=1 time_begin=time.time() t1=mythread() t1.start() t1.join() time_end=time.time() print(time_end-time_begin)#6.194466590881348
两个线程
import threading import time class mythread(threading.thread): def __init__(self,): threading.thread.__init__(self) def run(self): i=0 while i<100000000: i+=1 time_begin=time.time() t1=mythread() t2=mythread() t1.start() t2.start() t1.join() t2.join() time_end=time.time() print(time_end-time_begin)#11.998910427093506
可见计算密集型任务在python中并发并不能很好的节约时间,和串行差不多(在python以前版本中时间还会比串行多)
可是又有一个问题,我们的电脑不是有多核cpu吗,为什么不能同时两个cpu每个运行一个线程,那样时间就只有串行的一半啊?原因就是接下来讲的gil
5.gil
首先需要明确的一点是gil并不是python的特性,它是在实现python解析器(cpython)时所引入的一个概念。就好比c++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如gcc,intel c++,visual c++等。python也一样,同样一段代码可以通过cpython,pypy,psyco等不同的python执行环境来执行。像其中的jpython就没有gil。然而因为cpython是大部分环境下默认的python执行环境。所以在很多人的概念里cpython就是python,也就想当然的把gil归结为python语言的缺陷。所以这里要先明确一点:gil并不是python的特性,python完全可以不依赖于gil。
那么cpython实现中的gil又是什么呢?gil全称global interpreter lock为了避免误导,我们还是来看一下官方给出的解释:
in cpython, the global interpreter lock, or gil, is a mutex that prevents multiple native threads from executing python bytecodes at once. this lock is necessary mainly because cpython’s memory management is not thread-safe. (however, since the gil exists, other features have grown to depend on the guarantees that it enforces.)
gil就像是一个防止多线程并发的全局锁,gil的存在导致多线程无法很好的立即多核cpu的并发处理能力。python的多线程在多核cpu上,只对于io密集型计算产生正面效果;而当有至少有一个cpu密集型线程存在,那么多线程效率会由于gil而大幅下降。为了避免gil的影响,可以使用多进程。
这里具体参考https://www.cnblogs.com/sukiwx/p/8804974.html
6.守护线程setdaemon
当主线程完成时不需要某个子线程完全运行完就要退出程序,那么就可以将这个子线程设置为守护线程,setdaemon(true).
import threading import time class mythread(threading.thread): def __init__(self,name,age,second): threading.thread.__init__(self) self.name=name self.second=second self.age=age def run(self): print(self.name) time.sleep(self.second) print(self.age) time_begin=time.time() t1=mythread("sfencs",19,2) t2=mythread("tom",25,5) t1.setdaemon(true) t2.setdaemon(true) t1.start() t2.start() time_end=time.time() print(time_end-time_begin) ''' sfencs tom 0.003045797348022461 '''
不会显示年龄的输出,因为主线程已经结束。
7.lock锁
多线程中对同一资源进行处理,有可能会导致数据不安全
import time import threading def addnum(): global num #lock.acquire() temp=num time.sleep(0.001) num =temp-1 #lock.release() num = 100 thread_list = [] lock=threading.lock() for i in range(100): t = threading.thread(target=addnum) t.start() thread_list.append(t) for t in thread_list: t.join() print('final num:', num )#final num: 92
这里运行结果并不是0,原因是多个线程在time.sleep()的时候同时拿到了num,所以num是同一个数,解决方法就是加锁
8.死锁与递归锁
import threading import time mutexa=threading.lock() mutexb=threading.lock() class mythread(threading.thread): def run(self): self.func1() self.func2() def func1(self): mutexa.acquire() print('%s 拿到a锁' %self.name) mutexb.acquire() print('%s 拿到b锁' %self.name) mutexb.release() mutexa.release() def func2(self): mutexb.acquire() print('%s 拿到b锁' %self.name) time.sleep(2) mutexa.acquire() print('%s 拿到a锁' %self.name) mutexa.release() mutexb.release() if __name__ == '__main__': for i in range(5): t=mythread() t.start()
'''
thread-1 拿到a锁
thread-1 拿到b锁
thread-1 拿到b锁
thread-2 拿到a锁
'''
这里开了5个线程,可是却阻塞住了,原因是在thread1拿到b锁,thread2拿到a锁时,func2中在等待获得a锁,func1中在等待获得b锁,两者都在等待对方释放锁,造成了死锁,使得线程互相阻塞解决方法是使用递归锁rlock
import threading import time lock=threading.rlock() class mythread(threading.thread): def run(self): self.func1() self.func2() def func1(self): lock.acquire() print('%s 拿到a锁' %self.name) lock.acquire() print('%s 拿到b锁' %self.name) lock.release() lock.release() def func2(self): lock.acquire() print('%s 拿到b锁' %self.name) time.sleep(2) lock.acquire() print('%s 拿到a锁' %self.name) lock.release() lock.release() if __name__ == '__main__': for i in range(5): t=mythread() t.start() ''' thread-1 拿到a锁 thread-1 拿到b锁 thread-1 拿到b锁 thread-1 拿到a锁 thread-2 拿到a锁 thread-2 拿到b锁 thread-2 拿到b锁 thread-2 拿到a锁 thread-4 拿到a锁 thread-4 拿到b锁 thread-4 拿到b锁 thread-4 拿到a锁 thread-3 拿到a锁 thread-3 拿到b锁 thread-3 拿到b锁 thread-3 拿到a锁 thread-5 拿到a锁 thread-5 拿到b锁 thread-5 拿到b锁 thread-5 拿到a锁 '''
在python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁rlock。
这个rlock内部维护着一个lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源
9.信号量
信号量就相当于一个计数器,控制相同线程最大允许同时并发运行的数量
import threading import time s=threading.semaphore(3) class mythread(threading.thread): def __init__(self,name,age,second): threading.thread.__init__(self) self.name=name self.second=second self.age=age def run(self): s.acquire() print(self.name) time.sleep(self.second) print(self.age) s.release() t=[] for i in range(6): t.append(mythread("sfencs",19,2)) time_begin=time.time() for i in range(6): t[i].start() for i in range(6): t[i].join() time_end=time.time() print(time_end-time_begin) ''' sfencs sfencs sfencs 19 sfencs 19 sfencs 19 sfencs 19 1919 4.003796339035034 '''
如果没有信号量来限制,那么程序完成的时间应该为2秒左右
10.条件变量同步
wait等待notify的通知,当接到通知后,会重新从if accquire()执行
import threading import time c=threading.condition() count=0 class producer(threading.thread): def __init__(self): threading.thread.__init__(self) def run(self): global count while true: if c.acquire(): if count>5: c.wait() else: count+=1 print(self.name+":生产了一件商品") c.notify() c.release() time.sleep(0.5) class consumer(threading.thread): def __init__(self): threading.thread.__init__(self) def run(self): global count while true: if c.acquire(): if count<5: print(self.name+":我再等一会儿") c.wait() else: count-=1 print(self.name+":使用了一件商品") c.notify() c.release() time.sleep(0.5) for i in range(2): producer().start() for i in range(5): consumer().start()
11.event
事件(event)用于线程间同步和通信。比如线程a要完成某一任务(event)线程b才能执行后面的代码
set() 开始一个事件
wait() 如果未设置set状态会一直等待,否则过
clear() 清除set状态
isset() 是否设置set状态
import threading import time class interviewer(threading.thread): def __init__(self): threading.thread.__init__(self) def run(self): print("我能问你一个问题吗?") event1.set() event2.wait() print("我的问题刚才已经问完了") event2.clear() event1.set() class interviewee1(threading.thread): def __init__(self): threading.thread.__init__(self) def run(self): event1.wait() print("你问吧") event1.clear() event2.set() event1.wait() print("行吧。。。") event1.clear() event1=threading.event() event2=threading.event() t1 = interviewee1() t2 = interviewer() t1.start() t2.start()
'''
我能问你一个问题吗?
你问吧
我的问题刚才已经问完了
行吧。。。
'''
12.队列queue
说道多线程就不得不提到队列,python中的队列用到了queue模块,该模块提供了同步的,安全的对序列,包括fifo(先入先出)队列queue,lifo(后入先出)队列lifoqueue,和优先级队列priorityqueue.这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的通信
queue.qsize() 返回队列的大小
queue.empty() 如果队列为空,返回true,反之false
queue.full 与 maxsize 大小对应
queue.get([block[, timeout]])获取队列,timeout等待时间
queue.get_nowait() 相当queue.get(false)
queue.put(item) 写入队列,timeout等待时间
queue.put_nowait(item) 相当queue.put(item, false)
queue.task_done() 在完成一项工作之后,queue.task_done()函数向任务已经完成的队列发送一个信号
queue.join() 实际上意味着等到队列为空,再执行别的操作
这个程序将之前的一个用队列改写的
import threading import queue import time c=threading.condition() count=queue.queue() class producer(threading.thread): def __init__(self): threading.thread.__init__(self) def run(self): global count while true: if c.acquire(): if count.full(): c.wait() else: count.put(1) print(self.name+":生产了一件商品") c.notify() c.release() time.sleep(0.5) class consumer(threading.thread): def __init__(self): threading.thread.__init__(self) def run(self): global count while true: if c.acquire(): if not count.full(): print(self.name+":我再等一会儿") c.wait() else: count.get() print(self.name+":使用了一件商品") c.notify() c.release() time.sleep(0.5) for i in range(2): producer().start() for i in range(5): consumer().start()
上一篇: 关于Spring的69个问题
下一篇: ASP中数据库调用中常见错误的现象和解决