Python多線程/單線程


定義:
  • 進程:資源的集合,一個程序就是一個進程。
  • 線程:一個程序最小的運行單位。
import threading #引入線程模塊
import time
def run():
    time.sleep(5)
    print('over')

start_time=time.time()

run()
run()
run()
run()

end_time=time.time()
print('run_time', end_time-start_time)

#結果:run_time=20.38954234234
上面這個例子是單線程執行的,運行時間是20多秒,如果使用多線程,則會並行執行,執行結果應該是5秒左右。

  • 主線程等待子線程 

方法一:想要讓主線程等待添加的線程,需要先把創建的線程統一放到list里面,循環執行完,使用.join()方法,如下:

import threading
import time
def run():
    time.sleep(5)
    print('over')

start_time=time.time()
thread_list = []
for i in range(5):
    t=threading.Thread(target=run)  #實例化一個線程,target代表要指定執行什么,例如:target=run就是執行run()這個函數,指定函數名,不要加()
    #函數里面如果有參數,可以加上args,如:t=threading.Thread(target=run,args[1,2,3,]) ,有幾個參數,就在[]里面寫幾個參數
    t.start() #啟動這個線程
    thread_list.append(t) #把線程放到list里面 for thread in thread_list:
    thread.join() #主線程等待子線程
end_time=time.time()
print('run_time=', end_time-start_time) 

方法二:每一次循環都會判斷下剩余的線程是不是只剩下1個,不是一個的話,繼續循環,直到剩下的線程數為1時,繼續往下執行,如下:

import threading
import time

threads = []
start_time2 = time.time()
def insert_db():
    time.sleep(3)
    print('insert_db over')
for i in range(3):
    t = threading.Thread(target=insert_db)
    t.start()
while threading.activeCount()!=1:
#每一次循環都會判斷下剩余的線程是不是只剩下1個,不是一個的話,繼續循環,直到剩下的線程數為1時,繼續往下執行
    pass
end_time2 = time.time()
print('多線程執行的時間',end_time2 - start_time2)
print('鎖門...')
  • 線程 單個啟動 & 同時啟動

import  threading

'''單個啟動'''
for i in range(10):
    t = threading.Thread(target=func,args=[1])
    t.start()#start()在這里是一個一個線程拿過來,單個啟動

'''同時啟動啟動'''
ts = []
for i in range(10):
    t = threading.Thread(target=func,args=[1])
    ts.append(t)#先10個線程放到一個list里面
for t in ts:
    t.start() #循環同時啟動

練習分析:

import threading
import time
def insert_db():
    time.sleep(3)
    print('insert_db over')
start_time = time.time()
for i in range(3):
    t = threading.Thread(target=insert_db)
    t.start()
end_time = time.time()
print('多線程執行的時間',end_time - start_time)

結果:
多線程執行的時間 0.0010001659393310547
insert_db over
insert_db over
insert_db over

我們看到的結果是中運行時間是0.0010001659393310547,還不到一秒鍾,而我們代碼所設置的是最少也會需要3秒鍾,為什么時間會是這么短呢?

——因為所得到的時間只是主線程執行完它的工作時間,並不包括子線程執行的額時間。 

壓測練習:

import threading
import requests
def request():
    while True:
        r = requests.get('http://api.nnzhp.cn/api/user/stu_info?stu_name=%E7%8E%8B%E5%B0%8F%E6%9C%88')
        print(r.json())

for i in range(10):
    t = threading.Thread(target=request)
    t.start()

運行后會一直死循環,一直壓測,可以加入壓測的時間

單線程下載網頁:

import requests,time,threading
from hashlib import md5
result_list = {}
def down_load_pic(url):
    req = requests.get(url)
    m = md5(url.encode()) #把url轉換為二進制md5下
    file_name = m.hexdigest()+'.png' #拼接文件名
    with open(file_name ,'wb') as fw:
        fw.write(req.content)
    # return file_name
    result_list[file_name] = threading.current_thread()

url_list = ['http://www.nnzhp.cn/wp-content/uploads/2019/10/f410afea8b23fa401505a1449a41a133.png',
            'http://www.nnzhp.cn/wp-content/uploads/2019/11/481b5135e75c764b32b224c5650a8df5.png',
            'http://www.nnzhp.cn/wp-content/uploads/2019/11/b23755cdea210cfec903333c5cce6895.png',
            'http://www.nnzhp.cn/wp-content/uploads/2019/11/542824dde1dbd29ec61ad5ea867ef245.png']

start_time = time.time()
for url in url_list:
    down_load_pic(url)
end_time = time.time()

print(end_time - start_time)

#結果:4.439253807067871

多線程下載網頁:

import threading
import requests
import hashlib
import time
def down_load(url):
    name = hashlib.md5(url.encode()).hexdigest() #把url使用md5轉化為密文
    r = requests.get(url)
    with open('%s.jpg'%name,'wb') as fw:
        fw.write(r.content)

l = [
    'http://www.nnzhp.cn/wp-content/themes/QQ/images/logo.jpg',
    'http://www.nnzhp.cn/wp-content/uploads/2016/12/2016aj5kn45fjk5-150x150.jpg',
    'http://www.nnzhp.cn/wp-content/themes/QQ/images/thumbnail.png'
]

for i in l:
    t = threading.Thread(target=down_load,args=[i])
    #參數只有一個時,要使用()的方式來寫,需要多加一個逗號,如:args=(i,)
    # args是存參數的,如果里面只有一個參數的話,一定要在這個參數后面加一個逗號,因為是保存在元組里,如果不加逗號,它會默認為是字符串   應該寫成:args=(url,)
    t.start()

while threading.activeCount()!=1:
    pass
print('down load over...'
一個進程里面至少有一個線程,這個線程就是主線程。
主線程只是調度用的,它把子線程招來之后就完事了,因此如果要統計運行時間,必須要讓主線程等待所有的子線程都執行完后再記錄結束時間。
case_result = []

def run_case(case_name):
    print('run case..',case_name)
    case_result.append(  {case_name:'success'})
#多線程運行函數時,函數的返回值是拿不到的,所以定義一個list
#把函數運行的結果都存進去就ok了 
  • 查看當前有多少個線程:threading.activeCount()

import threading #引入線程模塊
import time
def run():
    time.sleep(1)

for i in range(10):
    t = threading.Thread(target=run)
    t.start()

print('當前有%s個線程'%threading.activeCount()) #當前有幾個線程

結果:當前有11個線程
為什么會是11個線程而不是10個線程呢?我們明明是循環啟動了10個線程,結果怎么就是11個了呢?
因為本身有一個主線程在運行,好比是我們自己在干活,我們又找了10個人來一起干活,加上我們自己一共是11個人在干活。所有的程序默認就會有一個線程。

2、守護線程

  • 一旦主線程死掉,那么守護線程不管有沒執行完事,全部結束,相當於你是一個國王(主線程),你有很多仆人(守護線程),仆人都是為你服務的,一旦你死了,那么你的仆人都需要給你陪葬。
import threading
import time
def talk(name):
    print('正在和%s聊天'%name)
    time.sleep(200)

print("qq主窗口")
t = threading.Thread(target=talk,args=['劉一'])
t.setDaemon(True) #設置線程為守護線程
t.start()

time.sleep(5)

print('結束。。。')

結果:
qq主窗口
正在和劉一聊天
結束。。。

如果是沒有設置為守護線程,本身程序執行需要有200秒才可以結束,設置了守護線程后,主線程5秒到了就會結束,主線程結束了,剩下的正在執行的線程已經是守護線程了,不會繼續200秒結束,會立即跟隨主線程結束。 

3、線程鎖

  • 多個線程同時在操作同一個數據的時候,會有問題,就要把這個數據加個鎖,然后同一時間只能有一個線程操作這個數據了【多個人或是多線程在同時操作同一個數據時,可能會有問題,可以加下鎖】

  • 忘記了解鎖或是代碼運行時沒有處理異常而沒有走到解鎖那里,都會成為線程死鎖。

舉例理解:比如說我們家里的衛生間,男女共用的,如果你進去時,沒有鎖門,有可能會有其他的開門,所以你需要進去時把門鎖一下,不用了,再把鎖打開下。

import threading
        from threading import Lock
        num = 0
        lock = Lock()#申請一把鎖
        def run():
            global num
            lock.acquire(timeout = 3)#加鎖,timeout是指定下時間,可以不寫,如果指定了時間,超過后申請的鎖就會失效
            num+=1
            lock.release()#解鎖

        lis = []
        for i in range(5):
            t = threading.Thread(target=run)
            t.start()
            lis.append(t)
        for t in lis:
            t.join()
        print('over',num)
#多個線程操作同一個數據的時候,就得加鎖
import threading

num = 0
lock = threading.Lock() #申請一把鎖,實例化一把鎖

def add():
    global num
    # lock.acquire()#加鎖
    # num+=1
    # lock.release()#解鎖  #不解鎖,就會產生線程死鎖,會一直在等待
    with lock:#簡寫,用with也會幫你加鎖,解鎖
        num+=1

for i in range(20):
    t = threading.Thread(target=add,)
    t.start()

while threading.activeCount() !=1:
    pass
  •  查看當前哪個線程在執行:threading.current_thread()

import threading
count = 0
lock = threading.Lock()
def test():
    global count
    print(threading.current_thread())
    lock.acquire()#加鎖
    count+=1
    lock.release()#解鎖
    #線程死鎖

for i in  range(3):
    t = threading.Thread(target=test)
    t.start()

結果:
<Thread(Thread-1, started 9000)>
<Thread(Thread-2, started 4820)>
<Thread(Thread-3, started 10224)>

下面來個簡單的爬蟲,看下多線程的效果:

import threading
import requests, time

urls = {
    "baidu": 'http://www.baidu.com',
    "blog": 'http://www.nnzhp.cn',
    "besttest": 'http://www.besttest.cn',
    "taobao": "http://www.taobao.com",
    "jd": "http://www.jd.com",
}

def run(name, url):
    res = requests.get(url)
    with open(name + '.html', 'w', encoding=res.encoding) as fw:
        fw.write(res.text)

start_time = time.time()
lis = []
for url in urls:
    t = threading.Thread(target=run, args=(url, urls[url]))
    t.start()
    lis.append(t)
for t in lis:
    t.join()
end_time = time.time()
print('run time is %s' % (end_time - start_time))

#下面是單線程的執行時間
        # start_time = time.time()
        # for url in urls:
        #     run(url,urls[url])
        # end_time = time.time()
        # print('run time is %s'%(end_time-start_time))

4、多進程,上面說了Python里面的多線程,是不能利用多核CPU的,如果想利用多核CPU的話,就得使用多進程,python中多進程使用multiprocessing模塊。

  • 一個電腦有幾核的CPU,就只能同時運行幾個任務

       ——比如說我們的電腦是4核的CPU,只可以同時運行4個進程,但我們在實際使用中,如果是4核的CPU,運行的並不止是4個程序,這是因為CPU上下文切換的,這個程序運行完了, 

              會立即切換到另外一個運行,我們感覺不到。

import multiprocessing,time

lock = multiprocessing.Lock() #申請一把鎖
a = 1

def down_load():
    time.sleep(3)
    global a
    with lock: #使用with這個方法會自動的去判斷使用完了自動的關掉鎖釋放【with是自動管理上下文的,操作文件時也可以使用,會自動識別關閉】
        a+=1
    print("運行完了")

if __name__ == '__main__':
    for i in range(5):
        p = multiprocessing.Process(target=down_load)
        # p=multiprocessing.Process(target=down_load,args =[1,2]) #有參數了可以加上args
        p.start()

    while len(multiprocessing.active_children())!=0:#等待子進程結束
        pass

    print(multiprocessing.current_process())

    print('end')

5、進程池

還可以使用進程池來快速啟動幾個進程,使用進程池的好處的就是他會自動管理進程數,咱們只需要給他設置一個最大的數就ok了。有新的請求提交到Pool中時,如果池還沒有滿,那么就會創建一個新的進程用來執行該請求;但如果池中的進程數已經達到指定的最大值,那么該請求就會等待,直到池中有進程結束,才會用之前的進程來執行新的任務。【數據大時使用】

from multiprocessing import Pool
import os
 
 
def worker(msg):
    print("%s開始執行,進程號為%d" %  (msg,os.getpid()))
 
if __name__ == '__main__':
    
    po = Pool(3)  # 定義一個進程池,最大進程數3
    for i in range(0, 10):
        # Pool().apply_async(要調用的目標,(傳遞給目標的參數元祖,))
        # 每次循環將會用空閑出來的子進程去調用目標
        po.apply_async(func=worker,args=(i,))
        #第一個func參數指定運行的函數,第二個args是參數,沒有參數可以不寫
    print("----start----")
    po.close()  # 關閉進程池,關閉后po不再接收新的請求
    po.join()  # 等待po中所有子進程執行完成,必須放在close語句之后
    print("-----end-----")

 總結:

  • 線程是用來干活的,只有進程的話是沒辦法運行的,進程里其實是線程在具體干活的。

  • 線程和線程之間是互相獨立的。

  • 線程是在進程里面

  • 默認,一個進程里面只有一個進程

  • 進程相當於是一個工廠,線程相當於是工廠里面具體干活的一個人

  • 主線程,也就是程序一開始運行的時候,最初的那個線程

  • 子線程,通過thread類實例化的線程,都是子線程

  • 主線程等待子線程,執行結束后,主線程再去做別的操作

  • 主線程執行完它自己工作的時間,並不包括子線程執行的時間

  • Python里面的多線程利用不了多核的cpu,因為如果是在多個CPU上運行,運行的結果會不太一樣,所以加入一個全局解釋器鎖(GLI),保證線程都在同一個cpu運行
  • 多進程可以利用多核CPU
  • CPU密集型任務,用多進程  ->消耗的CPU比較多
  • IO(磁盤IO、網絡IO)密集型任務,用多線程   ->消耗IO比較多【IO是上傳、下載的意思】
  • 多線程,線程之間的的數據是共享的
  • 多進程,每個進程之間的數據是獨立的,正是因為每個進程都是獨立的,所以多進程里面加鎖是沒任何意義的。

擴展:

協程,只有有個線程在運行,每遇到消耗IO的地方就會立馬切換,在切換到另一個,等着這個IO結束了,再去拿到數據,性能比較好;如:nginx就是使用的協程。


任何付出都是值得的,會越來越好  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM