章節號 | 內容???????????? |
---|---|
1圖片格式(png) | 寬度大于620px,保持高寬比減低為620px |
12 | 123 |
1-1-1 | 方法 |
1-1-1 | 方法 |
第1章節?線程
-
1-1?線程—多線程執行(直接使用threading.Thread)
??導入threading模塊即可。
1 import threading
2 import time
3
4 def pri():
5 print("thread run")
6 time.sleep(1)
7
8 for i in range(5):
9 t = threading.Thread(target=pri)
10 t.start()
li@li-ThinkPad-T420s:~$ python3 15.py
thread run
thread run
thread run
thread run
thread run
??線程也是多任務的一種方式。
??程序就是代碼。進程是開始執行的代碼,它擁有部分系統分配給它的資源。
??每個進程都有一個主線程。
??主線程會等待子線程結束才退出。
-
1-2?線程—使用Thread子類完成多線程
??子線程執行同一個函數,互相不會干擾。但是通常不同線程執行的是不同函數。
import threading
import time
class mythr(threading.Thread):
def run(self):
for i in range(5):
time.sleep(1)
#self.name當前線程的名字
msg = self.name+","+str(i)
print(msg)
t=mythr()
t.start()
Thread-6,0
Thread-6,1
Thread-6,2
Thread-6,3
Thread-6,4
??子結束,父未回收,則子成為僵尸
??父結束,子還存活,則子成為孤兒
li@li-System-Product-Name:~$ ps -aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 225720 9480 ? Ss 15:28 0:01 /sbin/init splash
root 2 0.0 0.0 0 0 ? S 15:28 0:00 [kthreadd]
root 4 0.0 0.0 0 0 ? I< 15:28 0:00 [kworker/0:0H]
root 6 0.0 0.0 0 0 ? I< 15:28 0:00 [mm_percpu_wq]
root 7 0.0 0.0 0 0 ? S 15:28 0:00 [ksoftirqd/0]
root 8 0.0 0.0 0 0 ? I 15:28 0:00 [rcu_sched]
root 9 0.0 0.0 0 0 ? I 15:28 0:00 [rcu_bh]
root 10 0.0 0.0 0 0 ? S 15:28 0:00 [migration/0]
root 11 0.0 0.0 0 0 ? S 15:28 0:00 [watchdog/0]
root 12 0.0 0.0 0 0 ? S 15:28 0:00 [cpuhp/0]
root 13 0.0 0.0 0 0 ? S 15:28 0:00 [cpuhp/1]
root 14 0.0 0.0 0 0 ? S 15:28 0:00 [watchdog/1]
root 15 0.0 0.0 0 0 ? S 15:28 0:00 [migration/1]
root 16 0.0 0.0 0 0 ? S 15:28 0:00 [ksoftirqd/1]
root 17 0.0 0.0 0 0 ? I 15:28 0:00 [kworker/1:0]
root 18 0.0 0.0 0 0 ? I< 15:28 0:00 [kworker/1:0H]
root 19 0.0 0.0 0 0 ? S 15:28 0:00 [cpuhp/2]
root 20 0.0 0.0 0 0 ? S 15:28 0:00 [watchdog/2]
root 21 0.0 0.0 0 0 ? S 15:28 0:00 [migration/2]
root 22 0.0 0.0 0 0 ? S 15:28 0:00 [ksoftirqd/2]
root 23 0.0 0.0 0 0 ? I 15:28 0:00 [kworker/2:0]
root 24 0.0 0.0 0 0 ? I< 15:28 0:00 [kworker/2:0H]
root 25 0.0 0.0 0 0 ? S 15:28 0:00 [cpuhp/3]
.
.
.
??PID為0的進程,負責進程間調度。
??PID為1的進程,負責間接或簡介創建其他的進程。
??PID為1的進程,處理所有孤兒
進程。
-
1-3?線程—線程的執行順序
??主進程和子進程執行順序是不一定的,由操作系統的調度來決定。
import threading
import time
class mythr(threading.Thread):
def run(self):
for i in range(5):
time.sleep(1)
msg = self.name+","+str(i)
print(msg)
def test():
#生成5個線程,都從run()開始執行
for i in range(5):
print("test"+str(i))
t = mythr()
t.start()
test()
test0
test1
test2
test3
test4
Thread-6,0
Thread-7,0
Thread-8,0
Thread-9,0
Thread-10,0
Thread-6,1
Thread-7,1
Thread-8,1
Thread-9,1
Thread-10,1
Thread-6,2
Thread-7,2
Thread-8,2
Thread-10,2
Thread-9,2
Thread-6,3
Thread-7,3
Thread-8,3
Thread-10,3
Thread-9,3
Thread-6,4
Thread-7,4
Thread-10,4
Thread-8,4
Thread-9,4
??第一次執行有順序(創建有順序),但是一旦sleep后,先后就不確定了。
-
1-4?線程—線程共享全局變量
import threading
import time
g_num=0
def w1():
global g_num
for i in range(3):
g_num+=1
print("w1 num is %d"%g_num)
def w2():
global g_num
print("w2 num is %d"%g_num)
print("before thread,num is %d"%g_num)
t1=threading.Thread(target=w1)
t1.start()
#延時保證t1執行完畢
time.sleep(1)
t2=threading.Thread(target=w2)
t2.start()
before thread,num is 0
w1 num is 3
w2 num is 3
??↑這里說明一個問題,線程間,全局變量是共用的。
??上述代碼是刻意避免了多個線程同時訪問一個變量,因為這樣會導致一些問題。
??↑
進
程間,數據和代碼均分用,互不干涉。??↑
線
程間,數據公用,代碼分用,數據可以共享。??利弊各自參半。線程共享的問題如下↓
1 from threading import Thread
2
3 g_num = 0
4
5 def test1():
6 global g_num
7 for i in range(1000000):
8 g_num += 1
9 print("test1 %d" % g_num)
10
11
12 def test2():
13 global g_num
14 for i in range(1000000):
15 g_num += 1
16 print("test2 %d" % g_num)
17
18
19 t1 = Thread(target=test1)
20 t1.start()
21
22 t2 = Thread(target=test2)
23 t2.start()
24
25 print("final %d"%g_num)
li@li-ThinkPad-T420s:~$ python3 16.py
test1 1213667
final 1260000
test2 1283779
??↑兩個線程,各自對同一個全局變量進行自加1至100萬,最后結果按道理來說,應該是200萬。實際結果如上。
??原因分析
: g_num += 1是關鍵。這里在視覺上是一行代碼,但是CPU實際調度過程中卻至少需要2個單位時間來運行,首先是執行g_num + 1,然后再執行g_num=g_num + 1。如果CPU在連續的兩個時間片,執行的都是不同線程的加法操作或者賦值操作,那么實際運行結果就會和預想產生出入。
??↓我們稍微修改一下代碼,讓t1線程有足夠的時間來運行。看結果如何:
from threading import Thread
import time
g_num = 0
def test1():
global g_num
for i in range(1000000):
g_num += 1
print("test1 %d" % g_num)
def test2():
global g_num
for i in range(1000000):
g_num += 1
print("test2 %d" % g_num)
t1 = Thread(target=test1)
t1.start()
#讓主線程等t1線程3秒鐘
time.sleep(3)
t2 = Thread(target=test2)
t2.start()
print("final %d"%g_num)
li@li-System-Product-Name:~$ python3 1.py
test1 1000000
final 1051813
test2 2000000
??↑可以看到計算的記過已經符合預期了,但是最后打印的final的值為什么是這個呢?因為final是由主線程執行的,而主線程和t2線程是同時執行的,相當于兩條腿同時跑。那如何才能把final 的輸出也變成預期的值↓
from threading import Thread
import time
g_num = 0
def test1():
global g_num
for i in range(1000000):
g_num += 1
print("test1 %d" % g_num)
def test2():
global g_num
for i in range(1000000):
g_num += 1
print("test2 %d" % g_num)
t1 = Thread(target=test1)
t1.start()
time.sleep(3)
t2 = Thread(target=test2)
t2.start()
#讓主線程等t2線程也加完畢
time.sleep(3)
print("final %d"%g_num)
li@li-System-Product-Name:~$ python3 1.py
test1 1000000
test2 2000000
final 2000000
-
1-5?線程—列表當做參數傳遞到線程處理函數中
from threading import Thread
import time
nums = [11, 22, 33]
def test1(nums):
nums.append(44)
print("test1 nums", nums)
def test2(nums):
#等待t1進行添加44的操作,確保它執行完畢
time.sleep(1)
print("test2 get nums", nums)
t1 = Thread(target=test1, args=(nums,))
t1.start()
t2 = Thread(target=test2, args=(nums,))
t2.start()
print("final nums", nums)
li@li-System-Product-Name:~$ python3 2.py
test1 nums [11, 22, 33, 44]
final nums [11, 22, 33, 44]
test2 get nums [11, 22, 33, 44]
??↑列表是可變數據類型,可以直接進行引用修改。
-
1-6?線程—避免全局變量被錯誤的使用
??上述的示例代碼中,因為不同的線程并行執行,對全局變量產生了不符合預期的結果。關鍵就是一句核心的代碼g_num += 1
被拆分執行了。
??那么我們應該如何避免這種問題的發生?應該有那么幾種方式:
??1、讓各個線程在時間上串行執行(最笨,也是背離多任務初衷的方式,就是上述的sleep)
??2、讓一個線程不斷的等另一個線程執行完畢(輪詢)
??3、讓關鍵語句g_num += 1
強制不拆分執行(加互斥鎖)
??解決:2、讓一個線程不斷的等(這個等是耗費資源的等)另一個線程執行完畢,效率低下↓
??首先
設置一個全部的標志g_f,在兩個線程中分別對這個標志進行判斷,不管標志為什么值,肯定有一個線程是不滿足條件而不運行的。
??然后
在不滿足條件的線程中,使用while死循環來查看標志g_f是否符合自己的運行條件,是就運行,不是就繼續循環。記住要設置條件跳出while死循環。
from threading import Thread
import time
g_num = 0
g_f =0
def test1():
global g_num
global g_f
if g_f == 0:
for i in range(1000000):
g_num += 1
g_f=1
print("test1 %d" % g_num)
def test2():
global g_num
global g_f
#不停的判斷,為輪詢
while True:
if g_f==1:
for i in range(1000000):
g_num += 1
#加完直接退出循環
break
print("test2 %d" % g_num)
t1 = Thread(target=test1)
t1.start()
t2 = Thread(target=test2)
t2.start()
print("final %d"%g_num)
可以看到,全程都未用sleep函數
final 110183
test1 1000000
test2 2000000
-
1-7?線程—互斥鎖
??上述的例子中,用while True的方式等待,是很不經濟的做法。有沒有什么好的解決方式?有的,那就是互斥鎖。
??使用方式
:導入Lock庫,用lo=Lock()創建鎖,在多個線程的核心代碼前后加入lo.acquire()和lo.release(),一旦有一個線程上鎖成功,那么其他的鎖都會堵塞(一直等待)到這個鎖解開為止,期間不占CPU。
from threading import Thread, Lock
import time
g_num = 0
def test1():
global g_num
#兩個線程都搶上鎖,因為你不知道哪個線程先執行,但是只要某一個線程成功上鎖,另一個線程就會在上鎖處卡住等待這個鎖解開
lo.acquire()
for i in range(1000000):
g_num += 1
#兩個線程都要解鎖
lo.release()
print("test1 %d" % g_num)
def test2():
global g_num
#兩個線程都搶上鎖,因為你不知道哪個線程先執行,但是只要某一個線程成功上鎖,另一個線程就會在上鎖處卡住等待這個鎖解開
lo.acquire()
for i in range(1000000):
g_num += 1
#兩個線程都要解鎖
lo.release()
print("test2 %d" % g_num)
#創建一把鎖,這把鎖只能上一次,一旦被鎖就不能鎖第二次
lo=Lock()
t1 = Thread(target=test1)
t1.start()
t2 = Thread(target=test2)
t2.start()
print("final %d"%g_num)
test1 1000000
test2 2000000
final 215425
-
1-7?線程—幾個問題
??上面的例子中,有這么一個問題:我們加了鎖,讓一個線程從頭到位執行完100萬次加法,才解鎖,也就是說我們把一個多任務人為寫成了單任務,這是違背多任務的初衷的,那么如何解決這個問題呢?那就是把加鎖解鎖放到for循環內部去。這也是一個上鎖的原則:上鎖的代碼部分要最小化
from threading import Thread, Lock
import time
g_num = 0
def test1():
global g_num
for i in range(1000000):
lo.acquire()
g_num += 1
lo.release()
print("test1 %d" % g_num)
def test2():
global g_num
for i in range(1000000):
lo.acquire()
g_num += 1
lo.release()
print("test2 %d" % g_num)
#創建一把鎖,這把鎖只能上一次,一旦被鎖就不能鎖第二次
lo=Lock()
t1 = Thread(target=test1)
t1.start()
t2 = Thread(target=test2)
t2.start()
print("final %d"%g_num)
final 24696
test1 1993796
test2 2000000
??↑但是這也有取舍的問題,在運行過程中,明顯感覺運行速度比上鎖在for循環外慢,個人覺得這是頻繁的上鎖解鎖造成的系統額外開銷。
??解鎖后相當于是通知其他鎖,所以造成系統開銷不大
。
-
1-7?線程—使用非共享的變量
??不修改全局變量,不加鎖。
from threading import Thread, Lock
import time
def test1():
num =100
num+=1
print("test1 %d" % num,end="\n")
time.sleep(1)
print("test1 %d" % num,end="\n")
def test2():
num =100
print("test2 %d" % num)
t1 = Thread(target=test1)
t1.start()
t2 = Thread(target=test2)
t2.start()
test1 101
test2 100
test1 101
??↑上例代碼說明:不同的線程執行不同的函數,修改局部變量,結果是互不影響的。那么,如果兩個線程,執行的是同一個函數呢??????
from threading import Thread, Lock
#導入threading是為了使用threading.current_thread().name
import threading
import time
def test1():
print("now thread is %s"%threading.current_thread().name,end="**")
t1 = Thread(target=test1)
t1.start()
t2 = Thread(target=test1)
t2.start()
now thread is Thread-7now thread is Thread-6****li@li-System-Product-Name:~/Documents$
??↑首先我們把代碼改成如上所示,先測試在當前的環境下,線程的名字各是什么,方便之后使用,這里看到了是Thread-6和Thread-7(這個因電腦環境而異,不一定相同),下面我們改寫代碼:
from threading import Thread, Lock
#導入threading是為了使用threading.current_thread().name
import threading
import time
def test1():
print("now thread is %s ##"%threading.current_thread().name,end="**")
num =100
if threading.current_thread().name == "Thread-6":
num+=1
else:
time.sleep(2)
print("now thread is %s,num is %d"%(threading.current_thread().name,num))
t1 = Thread(target=test1)
t1.start()
t2 = Thread(target=test1)
t2.start()
now thread is Thread-6 ##**now thread is Thread-7 ##now thread is Thread-6,num is 101**
now thread is Thread-7,num is 100
??↑首先兩個線程都進入test1()函數執行,其中Thread-6進行加1操作,然后打印num值,Thread-7直接睡2秒,再打印num值,可以看到,各自打印的num值不同。
??小tip,注意“**”的位置,來分析線程和end=執行的關系。
??這得出了一個結論,線程對局部變量的修改,也是各自獨有的,并不共享。
??總結:
??1、多進程中,全局和局部的變量,都是獨享的。
??2、多線程中,全局變量共享,局部變量獨享。
??所以,線程的局部變量,不用加鎖,即便是多線程運行同一個函數。
-
1-8?線程—死鎖
??我等著你解鎖,你等著我解鎖,就產生了死鎖。
import threading
import time
class thr1(threading.Thread):
def run(self):
if lo1.acquire():
print(self.name+"is start")
time.sleep(1)
if lo2.acquire():
print(self.name+"end")
lo2.release()
lo1.release()
class thr2(threading.Thread):
def run(self):
if lo2.acquire():
print(self.name+"is start")
time.sleep(1)
if lo1.acquire():
print(self.name+"end")
lo1.release()
lo2.release()
#產生2把鎖
lo1=threading.Lock()
lo2=threading.Lock()
t1=thr1()
t2=thr2()
t1.start()
t2.start()
??如何防止死鎖呢?
??1、編寫程序的時候使用銀行家算法!
??2、添加超時的時間!
??lo.acquire()有個參數,如果blocking為True,則會堵塞。如果blocking為False,則不堵塞。
lo.acquire([blocking])
In [4]: ?a.acquire
Docstring:
acquire(blocking=True, timeout=-1) -> bool
(acquire_lock() is an obsolete synonym)
Lock the lock. Without argument, this blocks if the lock is already
locked (even by the same thread), waiting for another thread to release
the lock, and return True once the lock is acquired.
With an argument, this will only block if the argument is true,
and the return value reflects whether the lock is acquired.
The blocking operation is interruptible.
Type: builtin_function_or_method
??timeout=-1表示永不休止。
??下面我們加入超時時間,代碼如下:
import threading
import time
class thr1(threading.Thread):
def run(self):
if lo1.acquire():
print(self.name+"is start1")
time.sleep(1)
if lo2.acquire(timeout=3):
print(self.name+"end")
lo2.release()
lo1.release()
class thr2(threading.Thread):
def run(self):
if lo2.acquire():
print(self.name+"is start")
time.sleep(1)
if lo1.acquire(timeout=3):
print(self.name+"end")
lo1.release()
lo2.release()
#產生2把鎖
lo1=threading.Lock()
lo2=threading.Lock()
t1=thr1()
t2=thr2()
t1.start()
t2.start()
li@li-System-Product-Name:~$ python3 3.py
Thread-1is start1
Thread-2is start
Thread-1end
li@li-System-Product-Name:~$ python3 3.py
Thread-1is start1
Thread-2is start
Thread-2end
li@li-System-Product-Name:~$ python3 3.py
Thread-1is start1
Thread-2is start
li@li-System-Product-Name:~$
??↑可以看到,多次運行的結果是不一樣的,因為多線程調度后,lo1和lo2誰先被解鎖是不一定的。注意到還有一個end都沒有的情況。思考為什么會有這樣的情況。
-
1-9?線程—同步
??同步就是協同步調,按照預定的先后或次序運行。
??異步就是誰先執行不確定。
from threading import Thread, Lock
import time
class thr1(Thread):
def run(self):
while True:
if lo1.acquire():
print("thr1")
time.sleep(0.5)
lo2.release()
class thr2(Thread):
def run(self):
while True:
if lo2.acquire():
print("thr2")
time.sleep(0.5)
lo3.release()
class thr3(Thread):
def run(self):
while True:
if lo3.acquire():
print("thr3")
time.sleep(0.5)
lo1.release()
lo1=Lock()
lo2=Lock()
lo2.acquire()
lo3=Lock()
lo3.acquire()
t1=thr1()
t2=thr2()
t3=thr3()
t1.start()
t2.start()
t3.start()
thr1
thr2
thr3
thr1
thr2
thr3
thr1
thr2
thr3
??↑三個線程中,循環上解鎖!這些都是前人的思想結晶,要好好吸收消化。
??有規律,這里就是同步~
-
1-10?線程—解決耦合
??生產者與消費者模式:
from queue import Queue
import threading
import time
class Ser(threading.Thread):
def run(self):
global queue
count =0
while True:
if queue.qsize()<1000:
for i in range(100):
count=count+1
msg = self.name+"made stuff"+str(count)
queue.put(msg)
print(msg)
time.sleep(0.5)
class Xer(threading.Thread):
def run(self):
global queue
count =0
while True:
if queue.qsize()>100:
for i in range(3):
count=count+1
msg = self.name+" use "+queue.get()
print(msg)
time.sleep(1)
queue=Queue()
for i in range(500):
print("init stuff"+str(i))
queue.put("init stuff"+str(i))
for i in range(2):
p=Ser()
p.start()
for i in range(5):
x=Xer()
x.start()
??結果請自行運行查看。
??在編程過程中,要解決生成和處理之間速度不統一的問題,可以使用隊列queue來完成。縱觀很多設計模式,都會使用第三者來解耦。
??或者可以用讀寫文件
方式來完成。
??創業公司,買一個虛擬服務器,完成所有功能。但是規模擴大后,要把功能分散到多個服務器上,多個服務器間還要相互通信,這時候就體現出你程序的解耦性能了。
-
1-11?線程—ThreadLocal
↑↓