Python-多线程详解
一、介绍线程
1)什么是线程?
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程
2)为什么使用线程
线程在程序中是独立的、并发的执行流。与分隔的进程相比,进程中线程之间的隔离程度要小,它们共享内存、文件句柄 和其他进程应有的状态。 因为线程的划分尺度小于进程,使得多线程程序的并发性高。进程在执行过程之中拥有独立的内存单元,而多个线程共享 内存,从而极大的提升了程序的运行效率。 线程比进程具有更高的性能,这是由于同一个进程中的线程都有共性,多个线程共享一个进程的虚拟空间。线程的共享环境 包括进程代码段、进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信。 操作系统在创建进程时,必须为改进程分配独立的内存空间,并分配大量的相关资源,但创建线程则简单得多。因此,使用多线程 来实现并发比使用多进程的性能高得要多。
3)线程的优点
总结起来,使用多线程编程具有如下几个优点: 进程之间不能共享内存,但线程之间共享内存非常容易。 操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此使用多线程来实现多任务并发执行比使用多进程的效率高 python语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了python的多线程编程。
二、实现方式
导包
import threading import time from threading import Lock, Thread
1)普通创建多线程方式
import threading import time def run(n): print('task',n) time.sleep(1) print('2s') time.sleep(1) print('1s') time.sleep(1) print('0s') time.sleep(1) if __name__ == '__main__': t1 = threading.Thread(target=run,args=('t1',)) # target是要执行的函数名(不是函数),args是函数对应的参数,以元组的形式存在 t2 = threading.Thread(target=run,args=('t2',)) t1.start() t2.start()
2)自定义线程
继承threading.Thread来定义线程类,其本质是重构Thread类中的run方法
class MyThread(threading.Thread): def __init__(self,n): super(MyThread,self).__init__() #重构run函数必须写 self.n = n def run(self): print('task',self.n) time.sleep(1) print('2s') time.sleep(1) print('1s') time.sleep(1) print('0s') time.sleep(1) if __name__ == '__main__': t1 = MyThread('t1') t2 = MyThread('t2') t1.start() t2.start()
3)守护线程
下面这个例子,这里使用setDaemon(True)把所有的子线程都变成了主线程的守护线程, 因此当主线程结束后,子线程也会随之结束,所以当主线程结束后,整个程序就退出了。 所谓’线程守护’,就是主线程不管该线程的执行情况,只要是其他子线程结束且主线程执行完毕,主线程都会关闭。也就是说:主线程不等待该守护线程的执行完再去关闭。
def run(n): print('task',n) time.sleep(1) print('3s') time.sleep(1) print('2s') time.sleep(1) print('1s') if __name__ == '__main__': t=threading.Thread(target=run,args=('t1',)) t.setDaemon(True) t.start() print('end')
注:通过执行结果可以看出,设置守护线程之后,当主线程结束时,子线程也将立即结束,不再执行
4)主线程等待子线程结束
为了让守护线程执行结束之后,主线程再结束,我们可以使用join方法,让主线程等待子线程执行
def run(n): print('task',n) time.sleep(2) print('5s') time.sleep(2) print('3s') time.sleep(2) print('1s') if __name__ == '__main__': t=threading.Thread(target=run,args=('t1',)) t.setDaemon(True) #把子线程设置为守护线程,必须在start()之前设置 t.start() t.join() #设置主线程等待子线程结束 print('end')
5)多线程共享全局变量
线程时进程的执行单元,进程时系统分配资源的最小执行单位,所以在同一个进程中的多线程是共享资源的
g_num = 100 def work1(): global g_num for i in range(3): g_num+=1 print('in work1 g_num is : %d' % g_num) def work2(): global g_num print('in work2 g_num is : %d' % g_num) if __name__ == '__main__': t1 = threading.Thread(target=work1) t1.start() time.sleep(1) t2=threading.Thread(target=work2) t2.start()
6)互斥锁
由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据, 所以出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,可以定义多个锁,像下面的代码,当需要独占 某一个资源时,任何一个锁都可以锁定这个资源,就好比你用不同的锁都可以把这个相同的门锁住一样。 由于线程之间是进行随机调度的,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期, 我们因此也称为“线程不安全”。
def work(): global n lock.acquire() # 加锁 temp = n time.sleep(0.1) n = temp-1 print n lock.release() # 解锁 if __name__ == '__main__': lock = Lock() n = 100 l = [] # 创建 100 个线程 for i in range(100): p = Thread(target=work) l.append(p) p.start() for p in l: p.join()
7)递归锁
RLcok类的用法和Lock类一模一样,但它支持嵌套,在多个锁没有释放的时候一般会使用RLock类
def func(lock): global gl_num lock.acquire() gl_num += 1 time.sleep(1) print(gl_num) lock.release() if __name__ == '__main__': gl_num = 0 lock = threading.RLock() for i in range(10): t = threading.Thread(target=func,args=(lock,)) t.start()
8)信号量(BoundedSemaphore类)
互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据,比如厕所有3个坑, 那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去
def run(n,semaphore): semaphore.acquire() #加锁 time.sleep(3) print('run the thread:%s\n' % n) semaphore.release() #释放 if __name__== '__main__': num=0 semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时运行 for i in range(22): t = threading.Thread(target=run,args=('t-%s' % i,semaphore)) t.start() while threading.active_count() !=1: pass else: print('----------all threads done-----------')