1. 程式人生 > 實用技巧 >排序演算法-氣泡排序

排序演算法-氣泡排序

來自 :https://zhuanlan.zhihu.com/p/101434151

執行緒

  1. 執行緒與程序的聯絡:都是為了解決併發
  2. 執行緒與程序的區別:
  • 程序:計算機中最小的資源分配單位
  • 執行緒:程序中的一員,同一個程序之間的幾個執行緒共享一個程序的資源
  • 執行緒可以直接被CPU排程,因此執行緒是計算機中能被CPU排程的最小單位
  • 比如一個 qq 是一個程序兩個好友可以同時給你傳送訊息,你可以同時接收,還可以和多個人聊天等等(並行)比如視訊 app,線上觀看的同時可以快取,這種情況下可以看作是兩條執行緒
  • 程序:資料隔離(不同的業務,應放在不同的程序中,比如 qq 與微信)
  • 執行緒:資料共享、效率高
  • 程序可以利用多個 cpu
  • 理論上,執行緒也能利用多個 cpu,但是 Python 的執行緒是不能利用多核的
  • 比如一個程序裡有一個列表 l = [],程序裡有三個執行緒
  • 如果三個執行緒同時利用多個 cpu 給列表新增數字 1,即三個執行緒都執行 l.append(1)操作
  • 它們很有可能一開始都認為這個列表是空的,那麼結果很可能 l = [1],這樣就造成資料丟失了
  • 而如果不能利用多個 cpu,即如果一個執行緒利用了一個 cpu 其他的執行緒只能等它從 cpu 返回後再一個一個利用 cpu
  • 這樣列表 l 就能確保每次都新增數字1,結果就是 l = [1, 1, 1]

3. 由剛才的示例,可以引申出全域性直譯器鎖(GIL):

  • Cpython 直譯器設定的鎖早期 Python剛出現時,沒有多核的概念
  • 所以沒有考慮給資料加鎖這件事後來有了多核的概念
  • 而 Python 是解釋型語言,考慮到資料的安全及各種資料的記錄
  • 為了保證資料的安全,便設定了全域性直譯器鎖
  • 這個鎖導致了同一個程序之間的多個執行緒同一時刻只能有一個執行緒訪問 cpu

4. 為什麼要有執行緒

  • 執行緒的開啟和銷燬的速度都比程序要快
  • cpu在程序之間切換和線上程之間切換的效率,執行緒更高

5. 執行緒即然不能同時利用多個 cpu,它的影響有:

  • cpu 主要是用來做計算的
  • 程式中除了計算還有 IO 操作

6. 網路延遲的本質是 IO 操作

7. 執行緒與程序在程式碼裡執行的區別:

from threading import Thread

def func():
    print("我是子執行緒")

t = Thread(target=func)
t.start()
print("我是主執行緒")

# 執行結果:
我是子執行緒
我是主執行緒


# 注意:如果你看過我之前的文章,就會發現:
# 程序的執行順序是是先執行主程序,再執行子程序
from multiprocessing import Process

def func():
    print("我是子程序")

if __name__ == "__main__":
    p = Process(target=func)  # 這裡並不代表開啟了子程序
    p.start()                 # 開啟了一個子程序,並執行func()
    print("我是主程序")

# 執行結果:
我是主程序
我是子程序

8. 執行緒的特點

import os
import time
from threading import Thread

def func():
    time.sleep(1)
    print("我是子執行緒", os.getpid())

for i in range(10):
    t = Thread(target=func)
    t.start()
print("我是主執行緒")

# 執行結果:
我是主執行緒
我是子執行緒 29530
我是子執行緒 29530
我是子執行緒 29530
我是子執行緒 29530
我是子執行緒 29530
我是子執行緒 29530
我是子執行緒 29530
我是子執行緒 29530
我是子執行緒 29530
我是子執行緒 29530

上面的示例可以看出執行緒的兩個特點:

  • 執行緒只能利用單個 cpu,通過 os.getpid() 可證明
  • 子執行緒的 10 次列印是一次性全部出來的,說明執行緒效率很高

9. 執行緒也有三個狀態:就緒 阻塞 執行,有上面的示例來分析:

  • 執行是在 cpu 裡面的,全域性鎖也是針對這裡
  • 第一個子執行緒去 cpu 裡執行時,很快會到阻塞狀態,在這個狀態裡要休眠 1 秒
  • 而在這個 1s 時間裡,第一個執行緒已經從 cpu 返回,然後第二個、第三個...又依次去 cpu
  • 然後返回,速度非常快,1s 內 10 個子執行緒已經都到阻塞狀態了
  • 所以執行發現 10 個子執行緒幾乎同一時間內打印出結果

10. 注意:程式中 IO 操作是不佔用全域性直譯器鎖和 cpu 的

11. 執行緒中的傳參

import time
from threading import Thread


def func(i):
    time.sleep(1)
    print("我是子執行緒%s" % i)

for i in range(10):
    t = Thread(target=func, args=(i,))
    t.start()

# 執行結果:
我是子執行緒0
我是子執行緒3
我是子執行緒5
我是子執行緒7
我是子執行緒9
我是子執行緒4
我是子執行緒1
我是子執行緒6
我是子執行緒8
我是子執行緒2

12. 執行緒中的 join() 用法

import time
from threading import Thread


def func(i):
    time.sleep(1)
    print("我是子執行緒%s" % i)

t_lst = []
for i in range(10):
    t = Thread(target=func, args=(i,))
    t.start()
    t_lst.append(t)
for t in t_lst:
    t.join()  # 阻塞,直到子執行緒中的程式碼執行
print("所有的執行緒都執行完了")

# 執行結果:
我是子執行緒0
我是子執行緒4
我是子執行緒6
我是子執行緒3
我是子執行緒1
我是子執行緒8
我是子執行緒2
我是子執行緒5
我是子執行緒7
我是子執行緒9
所有的執行緒都執行完了

13. 執行緒中的資料共享

from threading import Thread


n = 100
def func():
    global n
    n -= 1

t_lst = []
for i in range(100):
    t = Thread(target=func)
    t.start()
    t_lst.append(t)
for t in t_lst:
    t.join()
print(n)        # 0

14. 守護執行緒

import time
from threading import Thread

def main():
    print("主執行緒開始執行")
    time.sleep(3)
    print("主執行緒執行結束")

def daemon_func():
    while 1:
        time.sleep(1)
        print("我是守護執行緒")

t = Thread(target=daemon_func)
t.setDaemon(True)
t.start()
main()

# 執行結果:
主執行緒開始執行
我是守護執行緒
我是守護執行緒
主執行緒執行結束
import time
from threading import Thread

def main():
    print("主執行緒開始執行")
    time.sleep(3)
    print("主執行緒執行結束")

def daemon_func():
    while 1:
        time.sleep(1)
        print("守護執行緒")

def thread_son():
    print("子執行緒開始執行")
    time.sleep(5)
    print("子執行緒執行結束")

t = Thread(target=daemon_func)
t.setDaemon(True)
t.start()
Thread(target=thread_son).start()  # 子執行緒
main()

# 執行結果:
子執行緒開始執行
主執行緒開始執行
守護執行緒
守護執行緒
主執行緒執行結束
守護執行緒
守護執行緒
子執行緒執行結束

# 注意,上面的 Thread(target=thread_son).start() 如果改為
# Thread(target=thread_son()).start()
# 那麼會先執行 thread_son(),執行結果就會是同步效果了

# 執行發現守護執行緒和守護程序不同
# 守護執行緒會守護主執行緒直到主執行緒結束
# 如果主執行緒要等待其他子執行緒,那麼守護執行緒在這段時間中仍然發揮守護作用

15. 開啟執行緒的第二種方式

from threading import Thread


class MyThread(Thread):

     def run(self):
         print("子執行緒", self.ident)

for i in range(10):
    t = MyThread()
    t.start()

# 執行結果:
子執行緒 139970079782656
子執行緒 139970071389952
子執行緒 139970079782656
子執行緒 139970071389952
子執行緒 139970079782656
子執行緒 139970071389952
子執行緒 139970054604544
子執行緒 139970079782656
子執行緒 139970071389952
子執行緒 139970062997248

16. 檢視執行緒 id 的第二種方法

import time
from threading import Thread, currentThread, enumerate, active_count


def func(i):
    time.sleep(1)
    print("我是子執行緒%s" % i, currentThread().ident)

for i in range(10):
    t = Thread(target=func, args=(i, ))
    t.start()

print(enumerate())
print(active_count())

# 執行結果:
[<_MainThread(MainThread, started 139970964113152)>, 
 <Thread(Thread-1, started 139970935011072)>, 
 <Thread(Thread-2, started 139970926618368)>, 
 <Thread(Thread-3, started 139970918225664)>, 
 <Thread(Thread-4, started 139970702472960)>, 
 <Thread(Thread-5, started 139970694080256)>, 
 <Thread(Thread-6, started 139970685687552)>, 
 <Thread(Thread-7, started 139970677294848)>, 
 <Thread(Thread-8, started 139970668902144)>, 
 <Thread(Thread-9, started 139970660509440)>, 
 <Thread(Thread-10, started 139970652116736)>]
11
我是子執行緒6 139970677294848
我是子執行緒8 139970660509440
我是子執行緒3 139970702472960
我是子執行緒1 139970926618368
我是子執行緒0 139970935011072
我是子執行緒7 139970668902144
我是子執行緒9 139970652116736
我是子執行緒5 139970685687552
我是子執行緒2 139970918225664
我是子執行緒4 139970694080256

17. 用多執行緒實現 socket server 基於 tcp 的併發

# server.py
import socket
from threading import Thread


sk = socket.socket()
sk.bind(("127.0.0.1", 8080))
sk.listen()

def talk(conn):
    while 1:
        conn.send("我會一直向客戶端傳送這個資訊".encode())

while 1:
    conn, addr = sk.accept()
    Thread(target=talk, args=(conn, )).start()

# 因為有 accept、send 等各種阻塞,因此實現 socket server 基於 tcp 的併發
# 只用多執行緒來實現即可,因為執行效率快
# 可建立多個一樣的客戶端同時執行
# client.py

import socket

sk = socket.socket()
sk.connect(("127.0.0.1", 8080))

while True:
    msg = sk.recv(1024)
    print(msg.decode())

# 我會一直向客戶端傳送這個資訊
# 我會一直向客戶端傳送這個資訊
# 我會一直向客戶端傳送這個資訊
# ...

18. 執行緒鎖

import time
from threading import Thread


n = 0
def func():
    global n
    tmp = n
    time.sleep(0.1)  # 延遲,相當於時間片輪轉
    n = tmp + 1

t_lst = []
for i in range(100):
    t = Thread(target=func)
    t.start()
    t_lst.append(t)

for t in t_lst:
    t.join()

print(n)    # 1

19. 注意:如果不加上時間延遲,結果就不一樣了

import time
from threading import Thread

n = 0
def func():
    global n
    tmp = n
    # time.sleep(0.1)  # 延遲,相當於時間片輪轉
    n = tmp + 1

t_lst = []
for i in range(100):
    t = Thread(target=func)
    t.start()
    t_lst.append(t)

for t in t_lst:
    t.join()
print(n)    # 100

20. 為了保證資料安全,加鎖

import time
from threading import Thread, Lock  # 互斥鎖

n = 0
def func(lock):
    global n
    with lock:
        n += 1

lock = Lock()
t_lst = []
for i in range(100):
    t = Thread(target=func, args=(lock, ))
    t.start()
    t_lst.append(t)

for t in t_lst:
    t.join()

print(n)  # 100

# 1.如果沒有多個執行緒操作同一變數的時候,就可以不用加鎖(在這裡就是不使用全域性變數)。因此寫程式程式碼時,為了保證資料的安全,儘量不要使用全域性變數

# 2.如果是執行基礎資料型別的內建方法,都是執行緒安全的。list.append, list.pop, list.extend, list.remove, dic.get["key"]等

21. 有趣的實驗:科學家吃麵問題

from threading import Lock, Thread
import time


noodle_lock = Lock()
fork_lock = Lock()

def eat1(name):
    noodle_lock.acquire()
    print("%s拿到麵條了" % name)
    fork_lock.acquire()
    print("%s拿到叉子了" % name)
    print("%s開始吃麵了" % name)
    time.sleep(0.2)
    fork_lock.release()
    print("%s將叉子放回" % name)
    noodle_lock.release()
    print("%s將麵條放回" % name)

def eat2(name):
    fork_lock.acquire()
    print("%s拿到叉子了" % name)
    noodle_lock.acquire()
    print("%s拿到麵條了" % name)
    print("%s開始吃麵了" % name)
    time.sleep(0.2)
    noodle_lock.release()
    print("%s將麵條放回" % name)
    fork_lock.release()
    print("%s將叉子放回" % name)

Thread(target=eat1, args=("小黑", )).start()
Thread(target=eat2, args=("小明", )).start()
Thread(target=eat1, args=("小紅", )).start()
Thread(target=eat2, args=("小花", )).start()

# 執行結果:
小黑拿到麵條了
小黑拿到叉子了
小黑開始吃麵了
小黑將叉子放回
小明拿到叉子了
小紅拿到麵條了
小黑將麵條放回

# 注意:程式沒有停止,而是卡在某一點,這就是一種死鎖現象
# 形成該現象的本質原因:做某件事,需要同時拿到這兩個資源,但是現在卻有兩個鎖,鎖了這兩個資源

22. 遞迴鎖

from threading import Thread, RLock # 遞迴鎖
import time

noodle_lock = fork_lock = RLock()

def eat1(name):
    noodle_lock.acquire()
    print("%s拿到麵條了" % name)
    fork_lock.acquire()
    print("%s拿到叉子了" % name)
    print("%s開始吃麵了" % name)
    time.sleep(0.2)
    fork_lock.release()
    print("%s將叉子放回" % name)
    noodle_lock.release()
    print("%s將麵條放回" % name)

def eat2(name):
    fork_lock.acquire()
    print("%s拿到叉子了" % name)
    noodle_lock.acquire()
    print("%s拿到麵條了" % name)
    print("%s開始吃麵了" % name)
    time.sleep(0.2)
    noodle_lock.release()
    print("%s將麵條放回" % name)
    fork_lock.release()
    print("%s將叉子放回" % name)

Thread(target=eat1, args=("小黑", )).start()
Thread(target=eat2, args=("小明", )).start()
Thread(target=eat1, args=("小紅", )).start()
Thread(target=eat2, args=("小花", )).start()

# 執行結果:
小黑拿到麵條了
小黑拿到叉子了
小黑開始吃麵了
小黑將叉子放回
小黑將麵條放回
小明拿到叉子了
小明拿到麵條了
小明開始吃麵了
小明將麵條放回
小明將叉子放回
小紅拿到麵條了
小紅拿到叉子了
小紅開始吃麵了
小紅將叉子放回
小紅將麵條放回
小花拿到叉子了
小花拿到麵條了
小花開始吃麵了
小花將麵條放回
小花將叉子放回

23. 單執行緒演示

from threading import Lock

lock = Lock()
lock.acquire()
print(1)
lock.acquire()
print(2)

# 執行結果:
1
# 注意:程式同樣沒有結束,但可以說明
# 遞迴鎖可以快速解決死鎖問題,缺點是佔用資源

24. 不用遞迴鎖的解決方案

from threading import Lock, Thread
import time

lock = Lock()

def eat1(name):
    lock.acquire()
    print("%s拿到麵條了" % name)
    print("%s拿到叉子了" % name)
    print("%s開始吃麵了" % name)
    time.sleep(0.2)
    print("%s將叉子放回" % name)
    print("%s將麵條放回" % name)
    lock.release()

def eat2(name):
    lock.acquire()
    print("%s拿到叉子了" % name)
    print("%s拿到麵條了" % name)
    print("%s開始吃麵了" % name)
    time.sleep(0.2)
    print("%s將叉子放回" % name)
    lock.release()

Thread(target=eat1, args=("小黑", )).start()
Thread(target=eat2, args=("小明", )).start()
Thread(target=eat1, args=("小紅", )).start()
Thread(target=eat2, args=("小花", )).start()

# 執行結果:
小黑拿到麵條了
小黑拿到叉子了
小黑開始吃麵了
小黑將叉子放回
小黑將麵條放回
小明拿到叉子了
小明拿到麵條了
小明開始吃麵了
小明將叉子放回
小紅拿到麵條了
小紅拿到叉子了
小紅開始吃麵了
小紅將叉子放回
小紅將麵條放回
小花拿到叉子了
小花拿到麵條了
小花開始吃麵了
小花將叉子放回

25. 死鎖現象:

    • 使用了多把鎖在一個執行緒內進行了多次 Acquire 導致了不可恢復的阻塞
    • 形成原因——兩個鎖鎖了兩個資源,要做某件事需要同時拿到這兩個資源,多個執行緒同時執行這個步驟

26. 遞迴鎖與互斥鎖總結:

    • 遞迴鎖——不容易發生死鎖現象
    • 互斥鎖——使用不當容易發生死鎖
    • 遞迴鎖可以快速幫我們解決死鎖問題
    • 死鎖的真正問題不在於互斥鎖,而在於對互斥鎖的混亂使用
    • 要想真正的解決死鎖問題,還是要找出互斥鎖的問題進行修正才能解決根本問題

27. 執行緒中的佇列

    • 為什麼執行緒之間要有佇列,它不像程序,程序是需要資料共享時才用到佇列
    • 但是執行緒之間用佇列是為了在多個執行緒之間維持一個數據先後的秩序
    • 執行緒模組的佇列是執行緒之間資料安全的
import queue

q = queue.Queue()
# q.put() —— 佇列滿時會阻塞
# q.get() —— 佇列空時會阻塞
q.put(1)
print(q.get_nowait())       # 1

28. 異常情況

import queue

q = queue.Queue()
print(q.get_nowait())   

# 執行結果:
Traceback (most recent call last):
  File "test01.py", line 4, in <module>
    print(q.get_nowait())   
  File "/home/yanfa/anaconda3/lib/python3.7/queue.py", line 198, in get_nowait
    return self.get(block=False)
  File "/home/yanfa/anaconda3/lib/python3.7/queue.py", line 167, in get
    raise Empty
_queue.Empty

29. 異常處理

import queue
try:
    print(q.get_nowait())   # 在佇列為空時也不阻塞,這時會拋異常
except queue.Empty:
    pass



import queue
q = queue.Queue(3)
try:
    print(q.get_nowait())
except queue.Empty:
    pass
try:
    q.put_nowait(1)
except queue.Full:  # 這樣會造成資料丟失
    pass


# q.qsize():當前佇列中有多少個值
# q.empty():當前佇列是否為空
# q.full():當前佇列是否為滿

# 這三個沒什麼意義,不夠準確
# 比如執行緒佇列中有一個程序使用 q.empty() 詢問佇列是否為空
# 這個時候佇列是空的,所以返回資訊是空的
# 但是在這個資訊返回到這個執行緒之前,又有一個執行緒往佇列裡新增東西了
# 那麼佇列不為空了,但是之後第一個執行緒卻以為佇列是空的
# 其他兩個的道理一樣

30. 佇列

import queue

q = queue.LifoQueue()
q.put("a")
q.put("b")
q.put("c")
print(q.get())      # c
print(q.get())      # b
print(q.get())      # a

31. 優先順序佇列

import queue


q = queue.PriorityQueue()
q.put((2, "a"))
q.put((1, "b"))
q.put((3, "ac"))

print(q.get())      # (1, 'b')
print(q.get())      # (2, 'a')
print(q.get())      # (3, 'ac')

# 元組的第一個元素的數值越小,越先取出來(按 ascii 碼的值)
# 如果第一個元素一樣,則比較第二個元素的 ascii 碼的值

# 總結:一共有三個佇列
# queue:一般的佇列,先進先出
# queue.LifoQueue():後進先出
# queue.PriorityQueue():優先順序佇列

32. 執行緒池

import time
from threading import currentThread
from concurrent.futures import ThreadPoolExecutor


def func(i):
    time.sleep(1)
    print('子執行緒%s' % i,currentThread().ident)

tp = ThreadPoolExecutor(5)
for i in range(20):
    tp.submit(func,i)
tp.shutdown()

# 執行結果:
子執行緒0 140264401258240
子執行緒1 140264392865536
子執行緒3 140264169535232
子執行緒2 140264384472832
子執行緒4 140264161142528
子執行緒5 140264401258240
子執行緒6 140264392865536
子執行緒8 140264384472832
子執行緒9 140264161142528
子執行緒7 140264169535232
子執行緒10 140264401258240
子執行緒12 140264384472832
子執行緒13 140264161142528
子執行緒11 140264392865536
子執行緒14 140264169535232
子執行緒18 140264392865536
子執行緒15 140264401258240
子執行緒17 140264161142528
子執行緒16 140264384472832
子執行緒19 140264169535232
import time
import os
from concurrent.futures import ProcessPoolExecutor


def func(i):
    time.sleep(1)
    print('子程序%s'%i,os.getpid())

if __name__ == '__main__':
    tp = ProcessPoolExecutor(5)
    for i in range(20):
        tp.submit(func,i)
    tp.shutdown()

# 執行結果:
子程序0 3182
子程序1 3183
子程序3 3185
子程序2 3184
子程序4 3186
子程序5 3182
子程序6 3183
子程序9 3186
子程序7 3185
子程序8 3184
子程序10 3182
子程序12 3186
子程序11 3183
子程序13 3185
子程序14 3184
子程序15 3186
子程序16 3182
子程序17 3183
子程序18 3185
子程序19 3184
import time
from threading import currentThread
from concurrent.futures import ThreadPoolExecutor


def func(i):
    time.sleep(1)
    print('子執行緒%s' % i,currentThread().ident)

tp = ThreadPoolExecutor(5)
tp.map(func,range(20))


# 執行結果:
子執行緒3 140149244462848
子執行緒0 140149269640960
子執行緒1 140149261248256
子執行緒4 140149236070144
子執行緒2 140149252855552
子執行緒6 140149269640960
子執行緒8 140149236070144
子執行緒5 140149244462848
子執行緒7 140149261248256
子執行緒9 140149252855552
子執行緒11 140149236070144
子執行緒10 140149269640960
子執行緒13 140149261248256
子執行緒12 140149244462848
子執行緒14 140149252855552
子執行緒15 140149236070144
子執行緒18 140149244462848
子執行緒16 140149269640960
子執行緒17 140149261248256
子執行緒19 140149252855552

33. 使用 result 獲取子執行緒中的返回值

import time
from threading import currentThread
from concurrent.futures import ThreadPoolExecutor


def func(i):
    time.sleep(1)
    print('子執行緒%s' % i,currentThread().ident)
    return i**2

tp = ThreadPoolExecutor(5)
ret_l = []
for i in range(20):
    ret = tp.submit(func,i)
    ret_l.append(ret)

for ret in ret_l:
    print(ret.result())

# 執行結果:
子執行緒3 139623330207488
子執行緒0 139623426770688
子執行緒1 139623346992896
子執行緒2 139623338600192
0
子執行緒4 139623321814784
1
4
9
16
子執行緒8 139623338600192
子執行緒5 139623330207488
25
子執行緒6 139623426770688
36
子執行緒7 139623346992896
子執行緒9 139623321814784
49
64
81
子執行緒14 139623321814784
子執行緒10 139623338600192
子執行緒13 139623346992896
子執行緒11 139623330207488
100
121
子執行緒12 139623426770688
144
169
196
子執行緒16 139623338600192
子執行緒15 139623321814784
225
256
子執行緒17 139623346992896
289
子執行緒19 139623426770688
子執行緒18 139623330207488
324
361

34. 使用回撥函式來處理子執行緒中程式碼的執行效率

import time
from threading import currentThread
from concurrent.futures import ThreadPoolExecutor


def func(i):
    time.sleep(1)
    print('子執行緒%s' % i,currentThread().ident)
    return i**2

def callback(ret):
    print(ret.result())

tp = ThreadPoolExecutor(5)
for i in range(20):
    tp.submit(func,i).add_done_callback(callback)
tp.shutdown()

# 執行結果:
子執行緒3 140714030769920
9
子執行緒4 140714022377216
子執行緒2 140714039162624
16
4
子執行緒0 140714055948032
子執行緒1 140714047555328
0
1
子執行緒5 140714030769920
25
子執行緒6 140714022377216
36
子執行緒9 140714047555328
子執行緒7 140714039162624
子執行緒8 140714055948032
49
64
81
子執行緒10 140714030769920
100
子執行緒11 140714022377216
121
子執行緒13 140714055948032
169
子執行緒12 140714039162624
144
子執行緒14 140714047555328
196
子執行緒15 140714030769920
225
子執行緒16 140714022377216
256
子執行緒17 140714055948032
289
子執行緒18 140714039162624
324
子執行緒19 140714047555328
361