本文用简单的案例让读者理解 thread线程。
什么是线程:线程也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
thread机制:1、在python中,主线程结束后,会默认等待子线程结束后,主线程才退出。而在C/C++中,主线程结束后会把子线程kill掉。
2、如在一个线程1中调用thread2.join(),则thread2结束后,线程1才会接着thread2.join()往后运行。
3、主线程A启动了子线程B,调用b.setDaemaon(True),则主线程A结束时,会把子线程B也杀死。
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、如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期, 我们因此也称为“线程不安全”。