并发编程-多线程
多线程
什么是线程:
线程指的是一条流水线的工作过程的总称
线程是cpu的基本执行单位
对比进程而言,进程仅仅是一个资源单位其包含了程序运行所需的资源,就像一个车间
而单有资源是无法生产出产品的,必须有具体的生产产品的逻辑代码
线程就相当于车间中的一条流水线,而你的代码就是流水线上的一道道工序
特点:
1.每个进程都会有一个默认的线程
2.每个进程可以存在多个线程
3.同一进程中的所有线程之间数据是共享的
4.创建线程的开销远比创建进程小的多
主线程与子线程的区别:
1.线程之间是没有父子之分,是平等的
2.主线程是由操作系统自动开启的,而子线是由程序主动开启
3.即时主线程的代码执行完毕,也不会结束进程,会等待所有线程执行完毕,进程才结束
开启线程的两种方式:
1.实例化tread类,target参数用于指定子线程要执行的任务
from threading import thread def task(): print("子线程 run........") t = thread(target=task) t.start() print("over")
2.继承tread类,覆盖run方法
from threading import thread class mythread(thread): def run(self): print("子线程 run........") t = mythread() t.start() print("over")
与进程在使用方法上没有任何区别,不同的是开启子线程的代码可以写在任意位置
之所以使用方法完全相同是因为,多进程其实是为了弥补多线程的缺憾而诞生的。详见gil锁
线程与进程区别:
1.同一进程中 线程之间数据共享
a = 100 def task(): global a print("子线程 run........") a = 1 t = thread(target=task) t.start() print(a) # 1 print("over")
2.创建线程的开销远比创建进程小的多
from threading import thread from multiprocessing import process import time def task(): pass if __name__ == '__main__': start = time.time() for i in range(100): p = thread(target=task) p.start() print(time.time()-start) # 修改thread 为process类 查看结果
3.无论开启了多少子线程pid是不会变的
from threading import thread import os def task(): print(os.getpid()) for i in range(100): p = thread(target=task) p.start()
tread类的常用属性:
# threading模块包含的常用方法 import threading print(threading.current_thread().name) #获取当前线程对象 print(threading.active_count()) # 获取目前活跃的线程数量 print(threading.enumerate()) # 获取所有线程对象 t = thread(name="aaa") # t.join() # 主线程等待子线程执行完毕 print(t.name) # 线程名称 print(t.is_alive()) # 是否存活 print(t.isdaemon()) # 是否为守护线程
守护线程:
设置守护线程的语法与进程相同,相同的是也必须放在线程开启前设置,否则抛出异常。
守护线程的特点:
守护线程会在被守护线程结束后立即结束
from threading import thread import time def task(): print("start......") time.sleep(5) print("end......") t = thread(target=task) # t.setdaemon(true) t.daemon = true t.start() print("main over!")
疑惑:
from threading import thread import time def task(): print("start....1") time.sleep(3) print("end......1") def task2(): print("start....2") time.sleep(4) print("end......2") t = thread(target=task) t.daemon = true t.start() t2 = thread(target=task2) t2.start() print("main over!")
打印main over后主线程代码执行完毕,但是守护线程t1并没有立即结束,这是什么原因呢?
答:主线程会等待所有子线程执行完毕后结束
在上述例子中,一共有三个线程,主线程 ,t1,t2
虽然t1是守护线程 ,但是t2并不是所以主线程会等待t2执行结束才结束
顺序是:守护线程 等待 主线程 等待 其余子线程
换句话说,守护线程会随着所有非守护线程结束而结束。
线程锁
互斥锁
多线程的最主要特征之一是:同一进程中所有线程数据共享
一旦共享必然出现竞争问题。
a = 10 #lock = lock() def task(): global a #lock.acquire() b = a - 1 time.sleep(0.1) a = b #lock.release() for i in range(10): t = thread(target=task) t.start() for t in threading.enumerate(): if t != threading.current_thread(): t.join() print(a) # 输出 9
当多个线程要并发修改同一资源时,也需要加互斥锁来保证数据安全。
同样的一旦加锁,就意味着串行,效率必然降低。
死锁
现有两把锁l1和l2 用于表示盘子和筷子
两个线程的目标是吃饭,要吃饭的前提是同时拿到筷子和盘子,但是两个人的目标不同一个先拿筷子 ,一个先拿盘子最终造成死锁
l1 = lock() l2 = lock() def task(): l1.acquire() print(threading.current_thread().name,"拿到了筷子") time.sleep(0.1) l2.acquire() print(threading.current_thread().name, "拿到了盘子") print("吃饭") l1.release() l2.release() def task2(): l2.acquire() print(threading.current_thread().name, "拿到了盘子") l1.acquire() print(threading.current_thread().name,"拿到了筷子") print("吃饭") l2.release() l1.release() t1 = thread(target=task) t1.start() t2 = thread(target=task2) t2.start()
共有两把锁,但是一人拿到了一把,并且互不释放,相互等待,导致程序卡死,这就死锁。
要发生死锁只有两种情况
1.有不止一把锁,不同线程或进程分别拿到了不同的锁不放
2.对同一把锁执行了多次acquire
其中第二种情况我们可以通过可重入锁来解决
可重入锁
rlock 同一个线程可以多次执行acquire,释放锁时,有几次acquire就要release几次。
但是本质上同一个线程多次执行acquire时没有任何意义的,其他线程必须等到rlock全部release之后才能访问共享资源。
所以rlock仅仅是帮你解决了代码逻辑上的错误导致的死锁,并不能解决多个锁造成的死锁问题
# 同一把rlock 多次acquire #l1 = rlock() #l2 = l1 # 不同的rlock 依然会锁死 #l1 = rlock() #l2 = rlock() def task(): l1.acquire() print(threading.current_thread().name,"拿到了筷子") time.sleep(0.1) l2.acquire() print(threading.current_thread().name, "拿到了盘子") print("吃饭") l1.release() l2.release() def task2(): l2.acquire() print(threading.current_thread().name, "拿到了盘子") l1.acquire() print(threading.current_thread().name,"拿到了筷子") print("吃饭") l2.release() l1.release() t1 = thread(target=task) t1.start() t2 = thread(target=task2) t2.start()
忠告:在处理并发安全时 用完公共资源后一定要释放锁
信号量
semaphore
信号量也是一种锁,其特殊之处在于可以让一个资源同时被多个线程共享,并控制最大的并发访问线程数量。
如果把lock比喻为家用洗手间,同一时间只能一个人使用。
那信号量就可以看做公共卫生间,同一时间可以有多个人同时使用。
from threading import thread,semaphore,current_thread import time s = semaphore(3) def task(): s.acquire() print("%s running........" % current_thread()) time.sleep(1) s.release() for i in range(20): thread(target=task).start()
上一篇: 【第三篇】Python流程控制和运算符
下一篇: 16.Python略有小成(内置模块Ⅱ)