python-thread多线程通俗理解
本文用简单的案例让读者理解 thread线程。
什么是线程:线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
thread机制:1、在python中,主线程结束后,会默认等待子线程结束后,主线程才退出。而在C/C++中,主线程结束后会把子线程kill掉。
2、如在一个线程1中调用thread2.join(),则thread2结束后,线程1才会接着thread2.join()往后运行。
3、主线程A启动了子线程B,调用b.setDaemaon(True),则主线程A结束时,会把子线程B也杀死。
4、print(len(threading.enumerate())) 查看线程数量
案例一:炒菜与打乒乓球同步进行(主线程加一个子线程)
1 import threading 2 import time 3 4 def func(): 5 for _ in range(5): 6 print("线程1:打乒乓球") 7 time.sleep(1) 8 9 if __name__ == '__main__': 10 thread = threading.Thread(target=func)#创建子线程 11 thread.start() 12 for _ in range(5): 13 print('线程2:炒菜')#主线程和子线程轮流获得cpu资源 14 time.sleep(1)
案例二:炒股与打台球同步进行(主线程加两个子线程)
1 import threading 2 import time 3 4 def test1(): 5 for i in range(5): 6 print("炒股...%d"%i) 7 time.sleep(1) 8 9 def test2(): 10 for i in range(5): 11 print("打台球...%d"%i) 12 time.sleep(1) 13 14 t1 = threading.Thread(target=test1) 15 t2 = threading.Thread(target=test2) 16 t1.start() 17 t2.start() 18 #time.sleep(5) #如果放开等待时间,“收货精彩人生”将在最后打印 19 print("收获精彩人生")
案例三:吵架与打擦边球同步进行(设置保护线程)
1 import threading 2 import time 3 4 def test(): 5 for i in range(5): 6 print("子线程吵架:", i) 7 time.sleep(1) 8 9 if __name__ == '__main__': 10 t1 = threading.Thread(target=test) 11 t1.setDaemon(True)# 设置线程保护 12 t1.start() 13 time.sleep(2) 14 15 print("主线程打完擦边球后退出") 16 exit()
# 如果没有设置线程保护,即使主线程exit退出,子线程也会继续运行
案例四:生于忧患死于安乐(.join()指定线程优先执行)
1 import threading 2 #定义线程要调用的方法,*add可接收多个以非关键字方式传入的参数 3 def action(*add): 4 for arc in add: 5 print(threading.current_thread().getName() +" "+ arc)#调用 getName() 方法获取当前执行该程序的线程名 6 #定义为线程方法传入的参数 7 my_tuple = ("苦其心志",\ 8 "劳其筋骨",\ 9 "饿其体肤",\ 10 "空乏其身") 11 thread = threading.Thread(target = action,args =my_tuple) 12 thread.start() 13 thread.join() #指定线程优先执行完毕,完毕后才继续往下执行。 14 15 for i in range(5): #主线程执行如下语句 16 print(threading.current_thread().getName() + " 增益其所不能")
案例五:一千万次的反复无常(同步锁lock)
import threading num = 0 def add(): lock.acquire()#如果不加锁,频繁线程切换会导致两个线程数据不同步,最终num的值不是0. global num for i in range(10_000_000): num += 1 lock.release() def sub(): lock.acquire()#如果不加锁,频繁线程切换会导致两个线程数据不同步,最终num的值不是0. global num for i in range(10_000_000): num -= 1 lock.release() if __name__ == "__main__": lock = threading.Lock() subThread01 = threading.Thread(target=add) subThread02 = threading.Thread(target=sub) subThread01.start() subThread02.start() subThread01.join() subThread02.join() print("num result : %s" % num) #num为0
对于同步锁来说,一次acquire()必须对应一次release(),不能出现连续重复使用多次acquire()后再重复使用多次release()的操作,这样会引起死锁造成程序的阻塞,完全不动了。
案例六:千千万万万万千千个日夜(同步锁lock自加自解)
1 import threading 2 num = 0 3 4 def add(): 5 with lock: 6 # 自动加锁 7 global num 8 for i in range(10_000_000): 9 num += 1 10 # 自动解锁 11 def sub(): 12 with lock: 13 # 自动加锁 14 global num 15 for i in range(10_000_000): 16 num -= 1 17 # 自动解锁 18 19 if __name__ == "__main__": 20 lock = threading.Lock() # threading.Lock()对象中实现了enter__()与__exit()方法;换成递归锁.RLock有相同效果 21 subThread01 = threading.Thread(target=add) 22 subThread02 = threading.Thread(target=sub) 23 24 subThread01.start() 25 subThread02.start() 26 subThread01.join() 27 subThread02.join() 28 print("num result : %s" % num) # num返回0
案例七:循序渐进,以免固步自封(递归锁RLock,避免死锁)
1 import threading 2 3 num = 0 4 def add(): 5 lock.acquire() 6 lock.acquire()#如果是同步锁,连续两次acquire会发生死锁现象 7 global num 8 for i in range(10_000_000): 9 num += 1 10 lock.release() 11 lock.release() 12 def sub(): 13 lock.acquire() 14 lock.acquire() 15 global num 16 for i in range(10_000_000): 17 num -= 1 18 lock.release() 19 lock.release() 20 21 if __name__ == "__main__": 22 lock = threading.RLock() 23 subThread01 = threading.Thread(target=add) 24 subThread02 = threading.Thread(target=sub) 25 subThread01.start() 26 subThread02.start() 27 subThread01.join() 28 subThread02.join() 29 30 print("num result : %s" % num)
总结多线程
优点:
1、进程之间不能共享内存,但线程之间共享内存非常容易;
2、操作系统在创建进程时,需要为该进程重新分配系统资源,但创建线程的代价则小得多。因此使用多线程来实现多任务并发;
缺点:
1、由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以出现了线程锁,即同一时刻允许一个线程执行操作。线程锁用于锁定资源,可以定义多个锁,当需要独占某一个资源时,任何一个锁都可以锁定这个资源,就好比你用不同的锁都可以把这个相同的门锁住一样。
2、如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期, 我们因此也称为“线程不安全”。
命由己造,福由己求。谢天谢地,不忘祖先,敬畏圣贤。