一、线程
上一篇已经大致介绍了什么是线程以及线程的作用、多线程和进程的关系大致的总结如下:
- 线程是最小的执行单元,进程由至少一个线程组成;
- 进程和线程的调度,完全有操作系统决定,程序不能决定什么时候执行和执行多久。
- 一个应用程序可以有多进程、多线程
- 默认是单进程、单线程
- 单进程,多线程,在Python中不会性能提升,在Java和C#中可以提升
- 多线程: IO操作密集,一般不会用到CPU,效率提升是可以的
- 多进程:计算型操作密集, 需要占用CPU,因此性能不会有提升
概念说了很多下面来点实际,代码!
创建线程:
import threading def f1(arg): print(arg) t=threading.Thread(target=f1,args=(123,)) t.start()
简单吧这就是创建一个线程,也可以继承线程,自己创建
class MyThread(threading.Thread): #自己创建一个类,继承threading.Thread) def __init__(self,func,args): self.func=func self.args=args super(MyThread,self).__init__() #执行Mythread父类的构造函数 def run(self): #然后自己定义一个run方法 self.func(self.args) #执行f2函数 def f2(arg): print(arg) obj=MyThread(f2,123) #传参数进去 obj.start() #开始线程
threading常用方法:
threading.current_thread() 返回当前的线程变量。
threading.enumerate() 返回一个包含正在运行的线程的列表,正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.active_count() 返回正在运行的线程数量,与len(threading.enumerate())一样。
Thread
Thread是线程类,与Java类似,有两种使用方法,直接传入要运行的方法或从Thread继承并覆盖 run()。
PS: Thread中的run()方法,就是CPU来调度的时候执行的自动代用run方法
Thread构造方法(__init__):
group=None, target=None, name=None, args=(), kwargs=None, *, daemon=None
- group: 线程组
- target: 要执行的方法
- name: 线程名
- args/kwargs: 要传入的参数
常用方法:
- is_alive(): 返回线程是否在运行(启动前、终止前) 。
- getName(): 获取当前线程名
- setName(): 设置线程名
- isDaemon(): 获取线程是否是守护线程。
-
- 如果是后台线程,主线程执行过程中,后台线程也在运行,主线程执行完毕后,后台线程不论成功与否,全部都停止。
- 如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程夜之星完成后,程序停止
- start() : 启动线程
- join([timeout]): 阻塞当前上下文,直到调用此方法的线程终止或到达指定的timeout值
setDaemon(): 设置是否是守护进程
import threading import time def f1(arg): time.sleep(5) print(arg) t = threading.Thread(target=f1,args=(123,)) t.setDaemon(True) #True 表示主线程不等此子线程,默认值为False,也就是主线程等待子线程 t.start() #不代表当前线程会被立即执行 t.join(6) #表示主线程到此,等待。。 # 参数6,表示主线程在此最多等待6秒 print(33)
线程锁
lock
多线程的优势在于可以同时运行多个任务,而多个线程之间用到的数据可以共享进程的,效率很高(但在Python中多线程尽量应用在IO密集型的程序中)。正因为这样,所以存在数据不同步的问题.
为了避免资源争夺,所以引入了锁的概念。每当一个线程要访问共享数据时,必须先加把锁,而后在处理,处理完成后,解锁,让其他线程再来处理。
由于线程之间是进行随机调度的,每个线程可能只执行n条之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以线程锁就应用而生了。
实例方法:
- acquire([timeout]) 上锁, 使线程进入同步阻塞状态,尝试获得锁定。
- release() 释放锁,使用前线程必须已经获得锁定,否则抛出异常。
NUM = 10 def f1(args): global NUM NUM -= 1 print(NUM) for i in range(10): t = threading.Thread(target=f1,args=(i,)) t.start() #显示结果有可能会是 10个 0 其实我们想要的结果是 9 8 7......0 这就是产生了垃圾数据
那么我们来看看 上了锁之后会是神马效果:
NUM=10 def f1(l): global NUM l.acquire() #上锁 NUM-=1 time.sleep(1) print(NUM) l.release() #解锁 lock=threading.Lock() for i in range(10): t=threading.Thread(target=f1,args=(lock,)) t.start() #这样就能我们想要的结果了
RLOCK
RLock() 是一个可以被同一个线程请求多次的同步指令。Rlock使用了“拥有的线程" 和 "递归等级"的概念,处于锁定状态时,RLock被某个线程拥有。拥有RLock的线程可以再次调用acquire() , 释放锁时需要调用release()相同次数。说白了就是LOCK只能锁住一次,RLOCK可多次锁定递归
可以认为RLock包含一个锁定池和一个初始值为0的计数器,每次成功调用acquire()和release(),计数器将+1/-1,为0时处于未锁定状态。
NUM=10 def func(l): global NUM l.acquire() #上锁 NUM-=1 l.acquire() #上锁 time.sleep(1) l.release() #解锁 print(NUM) l.release() #解锁 # lock=threading.Lock() #只能锁一次 lock=threading.RLock() #锁多次 for i in range(10): t=threading.Thread(target=func,args=(lock,)) t.start()
Semaphore(信号量)
Lock和Rlock(互斥锁),发现同时只能允许一个线程来更改数据,执行顺序不还是串行的么,所谓的多线程就没有神马卵用了。而Semaphore是同时允许一定数量的线程来更改数据。可以理解成互斥锁的加强版,一个锁可以控制多个thread的访问,Lock的话,只能让一个线程来访问,Semaphore可以控制数量。
#指定放行几个 NUM=10 def func(l,i): global NUM l.acquire() #上锁 NUM-=1 time.sleep(2) print(NUM,i) l.release() #解锁 lock=threading.BoundedSemaphore(5) #锁5个线程 for i in range(15): t=threading.Thread(target=func,args=(lock,i)) t.start() #显示的结果 5 0 5 3 5 2 5 1 1 4 0 5 0 8 0 6 0 7 -4 9 -5 13 -5 10 -5 11 -5 12 -5 14