python多線程 DBUtils操作數據庫


import os, threading
from DBUtils.PooledDB import PooledDB
import pymysql, random, time
from queue import Queue


# from twisted.enterprise import adbapi
# from twisted.internet import reactor

# 創建一個有10個連接的mysql連接池.創建並維持10個線程並發寫入1000000條隨機數據到test.stu表中

class Test(threading.Thread):
def __init__(self, n):
super(Test, self).__init__()
mysql_conf = {
"host": "172.16.6.32",
"user": "root",
"passwd": "rootroot",
"charset": "utf8",
"db": "test",
"cursorclass": pymysql.cursors.DictCursor
}

# 創建一個連接池,連接池初始最多容納和創建25個連接,當連接池沒有可用連接則阻塞
# 使用連接池可以進行長連接,無需每次操作mysql時都建立連接,節省了建立連接的時間
self.n = n
self.pool = PooledDB(pymysql, maxconnections=0, blocking=True, **mysql_conf)
self.alpha = list("qwertyuiopasdfghjklzxcvbnm")
# self.sex = ["m", "s"]

def run(self):
print("%s號線程開始任務" % self.n)
sql = "insert into students(name,age) value(%s,%s)"

# 獲取連接
conn = self.pool.connection()
cursor = conn.cursor()

data_set = []
try:
for i in range(100):
name = "".join(random.sample(self.alpha, 20))
# sex = random.choice(self.sex)
age = random.randint(10, 60)
# classid = random.randint(1000, 9999)
data_set.append((name, age))
# print(data_set)
cursor.executemany(sql, data_set) # 批量操作,提高效率
conn.commit()
print("%s號線程完成任務" % self.n)
except:
# 如果出現錯誤,要回滾
conn.rollback()
print("%s號線程任務失敗" % self.n)
finally:
# 無論插入成功還是失敗,記得將連接放回連接池供其他線程使用,否則該線程會一直被占用
cursor.close()
conn.close() # 執行完sql操作后,將連接放回連接池,而不是真的關閉連接.如果不放回連接池,則該連接一直處於占用狀態,其他線程就無法使用該連接


# 最多創建10個線程並發執行
start_time = time.time()
thread_list = [] # 創建線程池
for i in range(1000):
thread_list.append(Test(i))
if len(thread_list) >= 1000: # 當列表中的線程有10個,就開始執行10個線程
print("線程數量為", len(thread_list))
print(i)
for thread in thread_list:
thread.start()

for thread in thread_list:
thread.join() # 10個線程都等待執行完,也就是說,10個線程有一個線程沒運行完就不能往下執行代碼; 這里會阻塞后面的thread_list=[]和print。但是多個線程間的join和join不會阻塞,也就是說執行完一個join還可以馬上執行下一個join,但是執行完最后一個join不能馬上執行 thread_list=[]

thread_list = [] # 當所有線程運行完清空線程池

print("總共用時:" + str(time.time() - start_time))
 

 

上面的例子要注意:

1. 必須等所有的線程都執行完start()后才能執行join(),而不能是一個線程執行一次start()和join(),下一個進程在執行一次start()和join(),這樣的話就是多個線程順序執行而不是並發執行,就和單線程沒有區別了。

2. 在上面的代碼中,執行1000次循環,每循環一次開啟一個線程,但是並發的線程只有10個,等10個線程完成執行完才能再開新的10個線程。這意味着,10個並發的線程中,有的線程先執行完,有的還沒有,必須等最慢的那個線程執行完才能開啟新的一批10個線程。這也意味着不是每時每刻都有10個線程在並發執行。而且,10個線程只要有一個線程阻塞住了,就不能生成新的線程執行任務,一直卡着不能工作。但是最理想的狀態是,10個線程如果有先執行完的就會新創建一個線程補上,時時刻刻保持有10個線程在並發運行。這點是我沒有做到的。

3. 每次使用完連接都要執行 conn.close() ,執行這一句不是真正的關閉連接,而是將連接放回連接池等待其他線程使用。如果不執行conn.close() 會導致線程不斷創建連接,超過了連接池能容納的最大連接數而報錯: pymysql.err.OperationalError,1040, u'Too many connections'

如果不調用join()等待線程執行完,也會導致這個問題,原因是線程沒有執行完就又開始產生新的線程,還沒來得及執行conn.close就又不斷產生的線程從而產生過多的mysql連接數。

4. 使用連接池可以復用長連接發送mysql請求,無需每次執行語句都連接一次。另外,使用批量操作也可以增加mysql操作的效率

 

 

關於Python中的多線程和多進程

首先

Python的多線程(threading.Thread)是無法使用多核的,這意味着python的多線程只能並發而不能並行。

Python的多進程(multiprocessing.Process)是可以使用多核的,這意味着python的多進程是可以並行,能夠充分利用CPU資源

 

為什么python的多線程不能利用多核CPU

因為在 python中有一個 GIL( Global Interpreter Lock),中文為:全局解釋器鎖

 

什么是GIL和為什么會用到GIL

GIL是一個互斥鎖,它防止多個線程同時執行Python字節碼(python代碼).。這個鎖是必要的,主要是因為CPython(Python解釋器)的內存管理不是線程安全的。怎么個不安全呢?

Python內部對變量或數據對象使用了引用計數器,我們通過計算引用個數,當個數為0時,變量或者數據對象就被自動釋放。

這個引用計數器需要保護,當多個線程同時修改這個值時,可能會導致內存泄漏;我們使用鎖來解決這個問題,可有時會添加多個鎖來解決,這就會導致另個問題,死鎖;

為了避免內存泄漏和死鎖問題,CPython使用了單鎖,即全局解釋器鎖(GIL),即執行Python字節碼都需要獲取GIL,而其他線程如果想要操作和執行相同的代碼需要等某個線程操作完了,釋放了GIL后,這個線程才能拿到GIL鎖,並且上鎖,執行代碼。

也就是說多線程共用一個GIL鎖,如果某線程需要執行,要等持有GIL鎖的線程釋放了鎖,才能獲取這個鎖才能執行。而多鎖則是每個線程使用一把鎖。

這可以防止死鎖,導致多線程同一時刻只能有一個線程在執行,也就是多線程並行而不是並發.

 

在 python多線程下,每個線程的執行方式如下: 
1、獲取GIL

2、執行代碼直到sleep或者是 python虛擬機將其掛起(比如執行IO操作)。 
3、釋放 GIL

一個進程中,同一時間只會有一個獲得了 GIL 的線程在跑,其它的線程都處於等待狀態等着 GIL 的釋放。 

多線程中,如果某個占有GIL鎖的線程在遇到 I/O 操作時會釋放這把鎖。如果是純計算的程序,沒有 I/O 操作,解釋器會每隔 
100次操作就讓線程釋放這把鎖,讓別的線程有機會執行代碼

 

一個進程會有自己的GIL鎖,所以如果是多進程的話,能夠使用到多核實現真正的並行。

 

關於python中多線程和多進程的適用場景

IO密集型操作

IO密集型操作,指的是頻繁讀寫文件,或者進行網絡請求的操作。IO操作無需CPU參與。

適合使用多線程

 

在操作系統中,我們知道IO操作的速度遠比CPU操作要慢,所以在執行IO操作時,線程或者進程會讓出CPU給其他線程或進程並進入阻塞狀態,而在該線程或者進程阻塞過程中,IO操作由IO設備獨立完成(主存和輔存的交互,如硬盤數據寫入內存或者內存數據寫入硬盤,這樣的操作可以有IO設備獨立完成,無需CPU參與)

在python多線程中,如果某個占有GIL鎖的正在運行的線程在執行IO操作,由於IO操作無需CPU參與並且需要等待,所以該線程就會釋放GIL鎖讓給其他線程執行。當其他線程在執行的時候,他們就會占用CPU資源。

 

多線程非常適合IO密集型操作,例如爬蟲或者數據庫的寫操作。

因為:單線程在進行IO操作等待時不能做其他事情,而多線程在IO操作等待時其他線程可以做其他事情如發出更多的IO請求

例如:

使用單線程進行爬數據或者數據庫操作,假設要爬100個網頁,每爬一個頁面都要等待響應,假如一個頁面平均等0.1秒才能獲取其內容。單線程要10s+才能執行完。

使用多線程,10個線程爬100個網頁,10個線程一起發出請求並一起等,10個線程在0.1秒后就得到響應,爬取到10個頁面。1s+就能執行完。因為等待的時間是平攤到每一個線程的。

當然單核執行多線程是並發交替執行,所以會進行線程間的切換,會損耗切換的時間。切換一次的時間短的可以忽略不計,但是如果線程數增加,切換的次數增加,切換的時間也會增加。在IO操作中,IO等待時間遠比切換消耗的時間大,所以多線程比單線程有利。

如果是CPU密集型操作,由於都是計算性的工作,沒有阻塞和等待的情況發生,此時多線程和單線程都是同一時刻只有一個線程在運算,但是多線程會消耗切換時間而單線程不會。結果是單線程反而比多線程快。

 

CPU密集型操作

CPU密集型操作指頻繁使用CPU的操作,如大規模的數據運算。

適合使用多進程

 

原因是python多線程是並發而不是並行,所以多線程和單線程都是同一時刻只有一個線程在運行,多線程由於頻繁切換反而比單線程慢。

python多線程會使用到多核,所以多線程間是並行的。在核數充足的情況下,一個主進程下有幾個子進程在運行就相當於有幾個人在同時工作。

 

 

轉自: 張柏沛IT技術博客 > python 多線程 + DBUtils連接池操作數據庫(附python之GIL)


免責聲明!

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



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