描述符使用
常使用的@classmethod屋剑、@staticmethd插勤、@property、甚至是__slots__等屬性都是通過描述符來實(shí)現(xiàn)的啦租。
# 1. 模擬 @classmethod
class Imitate_classmethod(object):
'''使用描述符模擬@classmethod'''
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
# 對傳入的函數(shù)加工,并返回加工后的函數(shù)
def maching_func(*args, **kwargs):
print('函數(shù)加工后荒揣,返回實(shí)例的類')
return self.func(owner, *args, **kwargs)
return maching_func
class Test(object):
msg = '這是一個(gè)對描述符模擬classmethod的測試'
# 這里是一個(gè)類裝飾器
@Imitate_classmethod
def about(cls):
print('print: ', cls.msg)
@Imitate_classmethod
def about_var(cls, value):
print('pirnt: {}, 傳入的值為:{}'.format(cls.msg, value))
# test
t = Test()
Test.about()
Test.about_var(666)
print('\n下面是Test實(shí)例的輸出:\n')
t.about()
t.about_var(777)
函數(shù)加工后篷角,返回實(shí)例的類
print: 這是一個(gè)對描述符模擬classmethod的測試
函數(shù)加工后,返回實(shí)例的類
pirnt: 這是一個(gè)對描述符模擬classmethod的測試, 傳入的值為:666
下面是Test實(shí)例的輸出:
函數(shù)加工后系任,返回實(shí)例的類
print: 這是一個(gè)對描述符模擬classmethod的測試
函數(shù)加工后恳蹲,返回實(shí)例的類
pirnt: 這是一個(gè)對描述符模擬classmethod的測試, 傳入的值為:777
'''
2. 模擬 @staticmethod
staticmethod方法與classmethod方法的區(qū)別在于:
classmethod方法在使用需要傳進(jìn)一個(gè)類的引用作為參數(shù)。
而staticmethod則不用俩滥。
'''
# 例子
class Imitate_staticmethod:
'''
使用描述符模擬@staticmethod
'''
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
#對傳進(jìn)函數(shù)進(jìn)行加工,最后返回該函數(shù)
def machining_func(*args, **kwargs):
print("函數(shù)加工處理后嘉蕾,返回實(shí)例的類")
return self.func(*args, **kwargs)
return machining_func
class Test:
@Imitate_staticmethod
def static_func(*args):
print("您輸入的是:",*args)
#測試
Test.static_func("檸檬","香蕉")
test = Test()
test.static_func(110, 112)
函數(shù)加工處理后,返回實(shí)例的類
您輸入的是: 檸檬 香蕉
函數(shù)加工處理后霜旧,返回實(shí)例的類
您輸入的是: 110 112
'''
3. 模擬 @property
在下面的代碼中错忱,
將描述符的回調(diào)結(jié)果存入對象字典中的好處是以后再執(zhí)行函數(shù)時(shí)就不會每一次都觸發(fā)描述的運(yùn)行,從而提高程序的效率挂据。
這樣以清,我們有再執(zhí)行同樣的函數(shù)時(shí),解釋器會先檢索對象的字典崎逃,
如果字典存在上次執(zhí)行結(jié)果的值玖媚,那就不用觸發(fā)描述符的運(yùn)行了。
在這個(gè)實(shí)驗(yàn)中必須強(qiáng)調(diào)的一點(diǎn)是描述符的優(yōu)先級婚脱,
我們想讓程序的描述符不能覆蓋實(shí)例屬性就必須使用非數(shù)據(jù)描述符。所以因需求不同勺像,描述符的優(yōu)先級也不同障贸。
'''
# 例子
class Imitate_property:
'''使用描述符模擬property'''
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
if instance is None:
return self
#回調(diào)傳入的函數(shù),將運(yùn)行結(jié)果保存在變量res中
res = self.func(instance)
#為函數(shù)名func.__name__設(shè)置一個(gè)值res后存入對象的字典中
setattr(instance, self.func.__name__, res)
return res
class Test:
def __init__(self, value):
self.value = value
@Imitate_property
def function(self):
return self.value**2
test = Test(2)
print(test.function) #輸出:4
print(test.__dict__) #輸出:{'value': 2, 'function': 4}
print(test.function) #輸出:4
print(test.function) #輸出:4
print(test.__dict__) #輸出:{'value': 2, 'function': 4}
4
{'value': 2, 'function': 4}
4
4
{'value': 2, 'function': 4}
錯(cuò)誤和異常
'''
try: # 在try中的異常后的代碼不會執(zhí)行吟宦,程序會跳轉(zhuǎn)到except中對異常進(jìn)行處理
pass # 需要捕獲異常的代碼塊
except ExceptName as result: # 捕獲異常ExceptName并將異常重命名為result
pass # 對捕獲異常的處理代碼塊
[
except Exp1 as result1: # except 捕獲語句可以有多條篮洁,不沖突即可
pass
...
]
Exception :是異常總和殃姓,所有異常都可被此異常捕獲
當(dāng)捕獲的異常和設(shè)定的異常名稱不對應(yīng)時(shí)袁波,會進(jìn)行系統(tǒng)異常處理。
try: # 開啟捕獲
pass
except Exp: # 捕獲到異常時(shí)執(zhí)行
pass
else: # 沒有捕獲到異常時(shí)執(zhí)行
pass
finally: # 收尾工作蜗侈,不論是否捕獲到異常都會執(zhí)行
pass
'''
捕獲多個(gè)異常
'''
將異常名放入元組中存儲
try:
pass
except (err1, err2 [,err...]):
pass
'''
# 異常 多個(gè)異常 例子
try:
1/0
open("sss0")
except NameError:
print("try中出現(xiàn)NameError的異常")
except Exception as result:
print("這里出現(xiàn)一個(gè)籠統(tǒng)的異常,{}".format(result))
else:
print("try中沒有異常時(shí)打印")
finally:
print("不管try中是否有異常篷牌,都會執(zhí)行finally")
print("異常測試結(jié)束")
# 例子結(jié)束
這里出現(xiàn)一個(gè)籠統(tǒng)的異常,division by zero
不管try中是否有異常,都會執(zhí)行finally
異常測試結(jié)束
異常的嵌套
'''
內(nèi)部try未捕獲到異常踏幻,向外部逐層傳遞
try:
try:
pass
except Exp1:
pass
pass
except Exp2:
pass
'''
自定義異常
'''raise'''
# 用戶自定義異常例子
class UserError(Exception):
def __init__(self):
print("這里是用戶自定義的異常")
try:
raise UserError
except UserError:
print("拋出自定義異常")
else:
print("沒有異常")
# 例子結(jié)束
這里是用戶自定義的異常
拋出自定義異常
異常處理中拋出異常
'''
try:
Error
except exception as result:
if (pass):
pass # 開啟捕獲異常
print(result)
else:
pass # 重寫拋出異常枷颊,此時(shí)的異常不會被捕獲,交由系統(tǒng)處理
raise
'''
調(diào)試
最簡單
斷言(assert)
在程序中可以用print的地方就可以用斷言(assert)
assert expression, throwException
表達(dá)式expression應(yīng)該為True,否則夭苗,拋出AssertionError:throwException
python -O fileName.py
關(guān)閉斷言
# assert 例子
def judger(name):
# name!='username'為True信卡,繼續(xù)運(yùn)行,F(xiàn)alse拋出your name is error!異常
assert name != 'username','your name is error!'
print('123456')
def main():
judger('username')
if __name__=='__main__':
main()
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
<ipython-input-1-4fda0f972b48> in <module>()
9
10 if __name__=='__main__':
---> 11 main()
12
<ipython-input-1-4fda0f972b48> in main()
6
7 def main():
----> 8 judger('username')
9
10 if __name__=='__main__':
<ipython-input-1-4fda0f972b48> in judger(name)
2 def judger(name):
3 # name!='username'為True题造,繼續(xù)運(yùn)行傍菇,F(xiàn)alse拋出your name is error!異常
----> 4 assert name != 'username','your name is error!'
5 print('123456')
6
AssertionError: your name is error!
logging
logging 不會拋出異常,可以把調(diào)試信息輸出到文件
logging 還可以指定記錄信息級別(debug, info, warning, error)
指定level = INFO
時(shí)界赔,logging.debug
就失效丢习,其他級別設(shè)定同理。
logging可以通過簡單的配置仔蝌,一條語句同時(shí)輸出到不同地方泛领,比如:console和文件。
import logging
logging.basicConfig(level=logging.INFO)
# logging 例子
import logging
def judger(name):
logging.info(name/0)
print('123456')
def main():
judger('username')
if __name__=='__main__':
main()
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-75192aa14816> in <module>()
10
11 if __name__=='__main__':
---> 12 main()
<ipython-input-3-75192aa14816> in main()
7
8 def main():
----> 9 judger('username')
10
11 if __name__=='__main__':
<ipython-input-3-75192aa14816> in judger(name)
3 def judger(name):
4 #
----> 5 logging.info(name/0)
6 print('123456')
7
TypeError: unsupported operand type(s) for /: 'str' and 'int'
pdb調(diào)試
pdb 調(diào)試有個(gè)明顯的缺陷就是對于多線程敛惊,遠(yuǎn)程調(diào)試等支持得不夠好渊鞋,
同時(shí)沒有較為直觀的界面顯示,不太適合大型的 python 項(xiàng)目瞧挤。
pdb操作指令
命令 | 簡寫 | 功能 | 注解 |
---|---|---|---|
break | b | 設(shè)置斷點(diǎn) | 無 |
continue | c | 繼續(xù)執(zhí)行程序 | 無 |
list | l | 查看當(dāng)前行的代碼 | 無 |
step | s | 進(jìn)入函數(shù) | 無 |
return | r | 執(zhí)行代碼直到從當(dāng)前函數(shù)返回 | 無 |
quit | q | 終止并退出 | 無 |
next | n | 執(zhí)行下一行 | 無 |
p | 打印變量的值 | 無 | |
help | h | 幫助 | 無 |
args | a | 查看傳入?yún)?shù) | 無 |
? | 回車 | 重復(fù)上一條命令 | 無 |
break | b | 顯示所有斷點(diǎn) | 無 |
break lineno | b lineno | 在指定行設(shè)置斷點(diǎn) | 無 |
break file:lineno | b file:lineno | 在指定文件的行設(shè)置斷點(diǎn) | 無 |
clear num | ? | 刪除指定斷點(diǎn) | 這里的num不是添加斷點(diǎn)的行號锡宋,而是斷點(diǎn)的序號 |
bt | ? | 查看函數(shù)調(diào)用棧幀 | 無 |
pdb交互調(diào)試
import pdb
pdb.run(funcName(attribute)) # 這里需要在pdb中先按s,然后才可以l特恬,不然不會顯示代碼
pdb程序里埋點(diǎn)
import pdb
pdb.set_trace() # 當(dāng)程序遇到這句話時(shí)才進(jìn)入調(diào)試
垃圾回收
參考網(wǎng)址:https://www.cnblogs.com/jin-xin/articles/9439483.html
概括:
同一代碼塊下:緩存機(jī)制
不同代碼塊:小數(shù)據(jù)池
代碼塊
一個(gè)模塊执俩、一個(gè)函數(shù)、一個(gè)類癌刽、一個(gè)文件都是一個(gè)代碼塊役首。
在交互式命令行下,一個(gè)命令就是一個(gè)代碼塊显拜。
代碼塊的緩存機(jī)制
Python在執(zhí)行同一個(gè)代碼塊的初始化對象的命令時(shí)衡奥,
會檢查是否其值是否已經(jīng)存在,如果存在远荠,會將其重用矮固。
換句話說:
執(zhí)行同一個(gè)代碼塊時(shí),遇到初始化對象的命令時(shí)譬淳,
他會將初始化的這個(gè)變量與值存儲在一個(gè)字典中档址,
在遇到新的變量時(shí),會先在字典中查詢記錄邻梆,
如果有同樣的記錄那么它會重復(fù)使用這個(gè)字典中的之前的這個(gè)值守伸。
滿足緩存機(jī)制則他們在內(nèi)存中只存在一個(gè),即:id相同浦妄。
代碼塊的緩存機(jī)制的適用范圍: int(float)含友,str替裆,bool。
int(float):任何數(shù)字在同一代碼塊下都會復(fù)用窘问。
bool:True和False在字典中會以1辆童,0方式存在,并且復(fù)用惠赫。
str:幾乎所有的字符串都會符合緩存機(jī)制
1把鉴,非乘法得到的字符串都滿足代碼塊的緩存機(jī)制
2,乘法得到的字符串分兩種情況:
2.1 乘數(shù)為1時(shí),任何字符串滿足代碼塊的緩存機(jī)制:
2.2 乘數(shù)>=2時(shí):僅含大小寫字母儿咱,數(shù)字庭砍,下劃線,
總長度<=20混埠,滿足代碼塊的緩存機(jī)制
優(yōu)點(diǎn):
能夠提高一些字符串怠缸,整數(shù)處理人物在時(shí)間和空間上的性能;
需要值相同的字符串钳宪,整數(shù)的時(shí)候揭北,直接從‘字典’中取出復(fù)用,避免頻繁創(chuàng)建和銷毀吏颖,提升效率搔体,節(jié)約內(nèi)存。
'''jupyter這里運(yùn)行存在問題半醉,以注釋為主'''
# 字符串緩存機(jī)制 例子
# 非乘法得到的字符串
def unMult():
s1 = 'string'
s2 = 'string'
print('非乘法得到的字符串')
print('*'*30)
print('s1和s2地址比較:',id(s1) == id(s2)) # True
print('*'*30)
# 乘數(shù)為1得到的字符串
def multOne():
s3 = 'asdf!@#$%^*'*1
s4 = 'asdf!@#$%^*'*1
print('乘數(shù)為1得到的字符串')
print('*'*30)
print('s3和s4地址比較:',id(s3) == id(s4)) # True
print('*'*30)
# 乘數(shù)>=2時(shí)
def multMore():
s3 = 'adf!'*4 # 包含其他字符疚俱,總長<20
s4 = 'adf!'*4
s5 = 'qwertyuiop[]asdfghjk!@#$%^&'*4
# 包含其他字符,總長>20
s6 = 'qwertyuiop[]asdfghjk!@#$%^&'*4
s7 = 'asdf_asdf'*3 # # 不包含其他字符缩多,總長<20
s8 = 'asdf_asdf'*3
print('乘數(shù)為>=2呆奕,總長度<=20,包含其他字符得到的字符串')
print('*'*30)
print('s3和s4地址比較:',id(s3) == id(s4)) # False
print('*'*30)
print('乘數(shù)為>=2,總長度>20,包含其他字符得到的字符串')
print('*'*30)
print('s5和s6地址比較:',id(s5) == id(s6)) # False
print('*'*30)
print('乘數(shù)為>=2衬吆,總長度<=20,不包含其他字符得到的字符串')
print('*'*30)
print('s7和s8地址比較:',id(s7) == id(s8)) # True
print('*'*30)
# test
unMult()
multOne()
multMore()
非乘法得到的字符串
******************************
s1和s2地址比較: True
s1和s2來源比較: True
******************************
乘數(shù)為1得到的字符串
******************************
s3和s4地址比較: True
s3和s4來源比較: True
******************************
乘數(shù)為>=2登馒,總長度<=20,包含其他字符得到的字符串
******************************
s3和s4地址比較: True
s3和s4來源比較: True
******************************
乘數(shù)為>=2,總長度>20,包含其他字符得到的字符串
******************************
s5和s6地址比較: True
s5和s6來源比較: True
******************************
乘數(shù)為>=2咆槽,總長度<=20,不包含其他字符得到的字符串
******************************
s7和s8地址比較: True
s7和s8來源比較: True
******************************
小數(shù)據(jù)池
大前提:
小數(shù)據(jù)池也是只針對 int(float),str圈纺,bool秦忿。
小數(shù)據(jù)池是針對不同代碼塊之間的緩存機(jī)制!6耆ⅰ灯谣!
Python自動將-5~256的整數(shù)進(jìn)行了緩存,
當(dāng)你將這些整數(shù)賦值給變量時(shí)蛔琅,并不會重新創(chuàng)建對象胎许,
而是使用已經(jīng)創(chuàng)建好的緩存對象。
python會將一定規(guī)則的字符串在字符串駐留池中創(chuàng)建一份。
'''小數(shù)據(jù)池例子'''
# 整數(shù)
a = 100
b = 100
c = 123456
d = 123456
print('id(a) == id(b): ',id(a) == id(b))
print('id(c) == id(d): ',id(c) == id(d))
id(a) == id(b): True
id(c) == id(d): False
指定駐留
# 指定駐留 例子
from sys import intern
a = 'asd*&'*3
b = 'asd*&'*3
print('未指定駐留:a is b:', a is b)
print(id(a))
print(id(b))
c = intern('asd*&'*3)
d = intern('asd*&'*3)
print('不未指定駐留:c is d:', c is d)
print(id(c))
print(id(d))
未指定駐留:a is b: False
139839647396976
139839643255088
不未指定駐留:c is d: True
139839642805680
139839642805680
引用計(jì)數(shù)
Python中以引用計(jì)數(shù)為主:
引用計(jì)數(shù)優(yōu)點(diǎn):簡單辜窑,實(shí)時(shí)性
引用計(jì)數(shù)缺點(diǎn):維護(hù)時(shí)占用內(nèi)存钩述,循環(huán)引用,造成內(nèi)存泄漏
引用計(jì)數(shù)減一不止del穆碎,還可以透過賦值None
隔代回收
Ruby中的垃圾回收是標(biāo)記清除牙勘,對內(nèi)存的申請是一次申請多個(gè),
當(dāng)內(nèi)存不夠用時(shí)再清理所禀,而Python中的內(nèi)存申請是用一個(gè)申請一個(gè)方面。
隔代回收為輔:
零代鏈表中檢測到相互循環(huán)引用的減一,得到是否是垃圾(引用計(jì)數(shù)為0)色徘,需要回收恭金,否則不會減一
GC模塊
'''
gc.get_count():獲取當(dāng)前自動執(zhí)行垃圾回收的計(jì)數(shù)器
返回一個(gè)元組(x,y,z)
參數(shù)解釋:
[
x 表示內(nèi)存占用,
y 表示零代清理次數(shù)褂策,
z 同理表示1代清理次數(shù)
]
gc.get_threshold() 獲取的gc模塊中自動執(zhí)行垃圾回收的頻率
返回(x,y,z)
參數(shù)解釋:
[
x表示清理上限横腿,
y表示每清理零代鏈表10次,
清理一次 1 代鏈表辙培。
z同理是1,2代 這里的x,y,z默認(rèn)值為(700,10,10)
]
查看一個(gè)對象的引用計(jì)數(shù):
sys.getrefcount()
gc.disable() 關(guān)閉Python的gc
gc.enable() 開啟gc
gc.isenabled() 判斷是否是開啟gc的
gc.collect([generation])顯式進(jìn)行垃圾回收(手動回收)
可以輸入?yún)?shù):
[
0代表只檢查第一代的對象蔑水,
1代表檢查一,二代的對象扬蕊,
2代表檢查一搀别,二,三代的對象尾抑,
如果不傳參數(shù)歇父,執(zhí)行一個(gè)full collection,也就是等于傳2再愈。
]
注意:
盡量不要手動重寫對象的__del__方法榜苫,
因?yàn)橹貙懞髸箘h除時(shí)不會自動調(diào)用gc來刪除,
此時(shí)需要調(diào)用父類的__del__()來刪除
'''
# gc 引用計(jì)數(shù) 示例
import gc, sys
a = 1
get_a_c = sys.getrefcount(a)
b = a
get_b_c = sys.getrefcount(a)
gc_c = gc.get_count()
gc_t = gc.get_threshold()
gc.disable()
gc.enable()
gc_is = gc.isenabled()
gc_cl = gc.collect(2)
print(get_a_c, get_b_c, gc_c, gc_t, gc_is, gc_cl)
2275 2275 (603, 5, 0) (700, 10, 10) True 921
內(nèi)置方法
參考網(wǎng)址:http://www.runoob.com/python3/python3-built-in-functions.html
'''
range(start, stop, step)
Python2中直接生成翎冲,Python3中生成器的生成方式
xrange():<Python3中重命名為range了>
Python2中:功能和range()一樣垂睬,但是是生成器形式的,需要next()調(diào)用
map(function, sequence[, sequence]) 返回list
參數(shù)解釋:
[
function 一個(gè)函數(shù)
sequence 一個(gè)或多個(gè)序列抗悍, 取決于function需要幾個(gè)參數(shù)
這里參數(shù)序列中的每一個(gè)元素分別調(diào)用function驹饺,
返回包含每次function的函數(shù)返回值的list
]
'''
# map功能演示
a = map(lambda x:x+x, [y for y in range(1,5,1)])
'''對list [1,2,3,4]中的每一個(gè)元素進(jìn)行自身疊加操作'''
print(a)
for i in a :
print(i) # 輸出疊加結(jié)果
<map object at 0x0000016E3AE1C3C8>
2
4
6
8
'''
filter(function, iterable) 用于過濾序列
參數(shù)解釋:
[
function 判斷函數(shù),返回True或False缴渊,
iterable 可迭代對象
]
'''
# filter 過濾奇數(shù)(不可被2整除)
def is_odd(num):
'''判斷函數(shù)赏壹,返回值為bool類型'''
return num % 2 == 1
l = [1,2,3,4,5,6,7,8,9,10]
newlist = filter(is_odd, l)
print('過濾結(jié)果:newlist: ', newlist)
print('得到奇數(shù)列表:', list(newlist))
過濾結(jié)果:newlist: <filter object at 0x0000016E3AEA0C88>
得到奇數(shù): [1, 3, 5, 7, 9]
'''
reduce(function, iterable[, initial]) 對可迭代對象進(jìn)行連續(xù)累積操作,
可以加減乘除等
參數(shù)解釋:
[
function(x,y) 處理函數(shù)衔沼,有兩個(gè)參數(shù)
iterable 可迭代對象
initial 可選參數(shù)蝌借,初始參數(shù)
]
function 同樣可是是lambda函數(shù)
在Python3中昔瞧,reduce合并到functools里
'''
# Python3 reduce()使用 例子:計(jì)算iterable的累加
from functools import reduce # 導(dǎo)入reduce
def add(x, y):
'''定義處理函數(shù):加操作'''
return x + y
numlist = [1,2,3,4,5,6] # 可迭代對象,要處理的對象
sum = reduce(add, numlist) # 對numlist實(shí)現(xiàn)累加操作
print(numlist,'累加結(jié)果:',sum)
# lambda函數(shù)實(shí)現(xiàn)累乘
produce = reduce(lambda x,y: x*y, numlist) # 結(jié)果為720
print(numlist,'累乘結(jié)果:',produce)
[1, 2, 3, 4, 5, 6] 累加結(jié)果: 21
[1, 2, 3, 4, 5, 6] 累乘結(jié)果: 720
'''
sorted(iterable, key=None, reverse=False) 對可迭代對象進(jìn)行排序(升序[A-Z])
參數(shù)解釋:
[
iterable 可迭代對象
key 主要用來比表的元素菩佑,具體可取自可迭代對象中自晰,
指定可迭代對象的一個(gè)元素
reverse 排序規(guī)則,reverse=True,降序[Z-A]擎鸠,reverse=False缀磕,升序[A-Z](默認(rèn))
]
sort() 應(yīng)用在list中
sorted() 應(yīng)用在所有可迭代對象
'''
# sort() sorted() 例子
alist = [1,2,45,3,12,6,33,44]
astring = 'asdfqwer1'
alist.sort()
sorted_str = sorted(astring)
print('sort(): ',alist,'\nsorted(): ', sorted_str)
sort(): [1, 2, 3, 6, 12, 33, 44, 45]
sorted(): ['1', 'a', 'd', 'e', 'f', 'q', 'r', 's', 'w']
pep8規(guī)則
pep8官網(wǎng)規(guī)范地址
https://www.python.org/dev/peps/pep-0008/
每級縮進(jìn) 4個(gè)空格
函數(shù)中的形參過長,換行時(shí)建議對齊
類間隔兩個(gè)空行
方法間隔一個(gè)空行
導(dǎo)入在單獨(dú)行
import os
import sys
導(dǎo)庫:標(biāo)準(zhǔn)庫劣光,第三方袜蚕,本地(個(gè)人)
禁止使用通配符導(dǎo)入
類名,駝峰命名绢涡,模塊牲剃,全程小寫,可以用下劃線
逗號雄可,冒號凿傅,分號之前避免空格
賦值等操作符前后不能因?yàn)閷R而添加多個(gè)空格
二元運(yùn)算符兩邊放置一個(gè)空格
關(guān)鍵字參數(shù)和默認(rèn)值參數(shù)的前后不要加空格
索引操作中的冒號當(dāng)作操作符處理前后要有同樣的空格(一個(gè)空格或者沒有空格)
函數(shù)調(diào)用的左括號之前不能有空格
最大行寬
限制所有行的最大行寬為79字符。
文本長塊数苫,比如文檔字符串或注釋聪舒,行長度應(yīng)限制為72個(gè)字符。
進(jìn)程
進(jìn)程定義
參考網(wǎng)址:https://www.cnblogs.com/gengyi/p/8564052.html
‘'''
進(jìn)程:
進(jìn)程是程序的一次動態(tài)執(zhí)行過程虐急,它對應(yīng)了從代碼加載箱残、
執(zhí)行到執(zhí)行完畢的一個(gè)完整過程。
進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位止吁。
進(jìn)程是由代碼(堆棧段)被辑、數(shù)據(jù)(數(shù)據(jù)段)、內(nèi)核狀態(tài)和一組寄存器組成敬惦。
在多任務(wù)操作系統(tǒng)中盼理,通過運(yùn)行多個(gè)進(jìn)程來并發(fā)地執(zhí)行多個(gè)任務(wù)。
由于每個(gè)線程都是一個(gè)能獨(dú)立執(zhí)行自身指令的不同控制流俄删,
因此一個(gè)包含多個(gè)線程的進(jìn)程也能夠?qū)崿F(xiàn)進(jìn)程內(nèi)多任務(wù)的并發(fā)執(zhí)行宏怔。
進(jìn)程是一個(gè)內(nèi)核級的實(shí)體,進(jìn)程結(jié)構(gòu)的所有成分都在內(nèi)核空間中畴椰,
一個(gè)用戶程序不能直接訪問這些數(shù)據(jù)臊诊。
進(jìn)程的狀態(tài):
創(chuàng)建、準(zhǔn)備迅矛、運(yùn)行、阻塞潜叛、結(jié)束秽褒。
進(jìn)程間的通信方式:
(1)管道(pipe)及有名管道(named pipe):
管道可用于具有親緣關(guān)系的父子進(jìn)程間的通信壶硅,有名管道除了具有管道所具有的功能外,它還允許無親緣關(guān)系進(jìn)程間的通信销斟。
(2)信號(signal):
信號是在軟件層次上對中斷機(jī)制的一種模擬庐椒,它是比較復(fù)雜的通信方式,
用于通知進(jìn)程有某事件發(fā)生蚂踊,一個(gè)進(jìn)程收到一個(gè)信號與處理器收到一個(gè)中斷請求效果上可以說是一致的约谈。
(3)消息隊(duì)列(message queue):
消息隊(duì)列是消息的鏈接表,它克服了上兩種通信方式中信號量有限的缺點(diǎn)犁钟,
具有寫權(quán)限的進(jìn)程可以按照一定得規(guī)則向消息隊(duì)列中添加新信息棱诱;對消息隊(duì)列有讀權(quán)限的進(jìn)程則可以從消息隊(duì)列中讀取信息。
(4)共享內(nèi)存(shared memory):
可以說這是最有用的進(jìn)程間通信方式涝动。
它使得多個(gè)進(jìn)程可以訪問同一塊內(nèi)存空間迈勋,不同進(jìn)程可以及時(shí)看到對方進(jìn)程中對共享內(nèi)存中數(shù)據(jù)得更新。
這種方式需要依靠某種同步操作醋粟,如互斥鎖和信號量等靡菇。
(5)信號量(semaphore):
主要作為進(jìn)程之間及同一種進(jìn)程的不同線程之間得同步和互斥手段。
(6)套接字(socket):
這是一種更為一般得進(jìn)程間通信機(jī)制米愿,它可用于網(wǎng)絡(luò)中不同機(jī)器之間的進(jìn)程間通信厦凤,應(yīng)用非常廣泛。
Python中進(jìn)程創(chuàng)建:
os.fork()
subprocess
processing
multiprocessing
'''
創(chuàng)建進(jìn)程
linux專供版
這里的fork()只是用于Linux(unix)僅作為了解
不要在jupyter notebook中進(jìn)行大量或多次fork執(zhí)行育苟,會大量占用cpu较鼓,導(dǎo)致卡頓
fork 側(cè)重于父子進(jìn)程都有任務(wù),適合于少量的子進(jìn)程創(chuàng)建
父進(jìn)程宙搬、子進(jìn)程執(zhí)行順序沒有規(guī)律笨腥,完全取決于操作系統(tǒng)的調(diào)度算法
import os
pid = os.fork()
fork()是唯一調(diào)用一次返回兩次的函數(shù),因?yàn)椴僮飨到y(tǒng)將父進(jìn)程復(fù)制一份形成新的進(jìn)程(子進(jìn)程)勇垛,然后分別在父進(jìn)程和子進(jìn)程內(nèi)返回脖母。
父進(jìn)程和子進(jìn)程都會從fork()函數(shù)中得到一個(gè)返回值,子進(jìn)程永遠(yuǎn)返回0,而父進(jìn)程返回子進(jìn)程的ID闲孤。
一個(gè)父進(jìn)程可以fork出很多子進(jìn)程谆级,所以,父進(jìn)程要記下每個(gè)子進(jìn)程的ID讼积,而子進(jìn)程只需要調(diào)用getppid()就可以拿到父進(jìn)程的ID肥照。
通過os.getpid()
獲取當(dāng)前進(jìn)程的pid通過os.getppid()
獲取當(dāng)前進(jìn)程父進(jìn)程的pid
這里可以把fork理解為鳴人影分身的deepcopy模式,完全克隆出一個(gè)自己勤众,對分分支后的任務(wù)進(jìn)行執(zhí)行
# fork() 例子
import os # fork()在os模塊下
def prt():
print('我在fork前')
prt()
def use_fork():
pid = os.fork() # 進(jìn)程在這里fork舆绎,產(chǎn)生一個(gè)新的進(jìn)程,返回新進(jìn)程的pid
if pid<0:
print('fork調(diào)用失敗')
elif pid == 0: # 子進(jìn)程
print('我是子進(jìn)程<{}>,我的父進(jìn)程為:<{}>'.format(os.getpid(), os.getppid()))
# 打印子進(jìn)程pid们颜,父進(jìn)程pid
else: # 父進(jìn)程
print('我是父進(jìn)程<{}>吕朵,我的子進(jìn)程為<{}>'.format(os.getpid(), pid))
# 打印父進(jìn)程pid猎醇,子進(jìn)程pid
print('父子進(jìn)程都可以執(zhí)行這行') # 分支執(zhí)行結(jié)束猾警,回歸執(zhí)行程序主干
use_fork()
我在fork前
我是父進(jìn)程<4134>瑰艘,我的子進(jìn)程為<5573>
父子進(jìn)程都可以執(zhí)行這行
我在fork前
我是子進(jìn)程<5573>,我的父進(jìn)程為:<4134>
父子進(jìn)程都可以執(zhí)行這行
'''多進(jìn)程中括荡,每個(gè)進(jìn)程中所有數(shù)據(jù)(包括全局變量)都各有擁有一份锉屈,互不影響'''
# fork() 內(nèi)修改全局變量 例子
import os # fork()在os模塊下
num = 12
print('未fork的全局變量:', num)
pid = os.fork() # 進(jìn)程在這里fork皇钞,產(chǎn)生一個(gè)新的進(jìn)程规丽,返回新進(jìn)程的pid
if pid<0:
print('fork調(diào)用失敗')
elif pid == 0: # 子進(jìn)程
print('我是子進(jìn)程<{}>,我的父進(jìn)程為:<{}>'.format(os.getpid(), os.getppid()))
num += 3
print('子進(jìn)程修改num為:{}'.format(num))
else: # 父進(jìn)程
print('我是父進(jìn)程<{}>绒怨,我的子進(jìn)程為<{}>'.format(os.getpid(), pid))
num += 5
print('父進(jìn)程修改num為:{}'.format(num))
print('num:',num) # 分支執(zhí)行結(jié)束造虎,回歸執(zhí)行程序主干
未fork的全局變量: 12
我是父進(jìn)程<6203>第队,我的子進(jìn)程為<6301>
父進(jìn)程修改num為:17
num: 17
我是子進(jìn)程<6301>,我的父進(jìn)程為:<6203>
子進(jìn)程修改num為:15
num: 15
'''多個(gè)fork()同時(shí)出現(xiàn)'''
# 多個(gè)fork()同時(shí)出現(xiàn)
import os, time
ret = os.fork()
if ret ==0:
print("1".center(10, "*"))
else:
print("2".center(10,"*"))
ret = os.fork()
if ret == 0:
print("11".center(10,"-"))
else:
print(print("22".center(10,"+")))
# 父進(jìn)程哮塞、子進(jìn)程執(zhí)行順序沒有規(guī)律,完全取決于操作系統(tǒng)的調(diào)度算法
# 注意:這里的結(jié)果中斥铺,包含了兩個(gè)11,22
# 這里和前邊說的在fork()處分開后彻桃,代碼都會分別執(zhí)行,
# 也就是后邊的fork()是在單個(gè)進(jìn)程中的再一次分進(jìn)程了
****1*****
****2*****
++++22++++
None
++++22++++
****2*****
----11----
None
----11----
getpid晾蜘、getppid
getpid()獲得當(dāng)前進(jìn)程的pid
getppid()獲得當(dāng)前進(jìn)程的父進(jìn)程
在新建進(jìn)程之后子進(jìn)程和父進(jìn)程執(zhí)行的基本一樣(這里只是不執(zhí)行父進(jìn)程專有的代碼邻眷,其他的代碼子進(jìn)程同樣會執(zhí)行)
全局變量在多個(gè)進(jìn)程中不共享
# getpid getppid 例子
import os, time
g_num = 666
tmp = os.fork()
if tmp == 0:
print("*"*20)
print(g_num)
g_num +=111
time.sleep(2)
print("this is son pid = {0}, ppid = {1}, g_num_id = {2}, g_num = {3}".format(os.getpid(), os.getppid(), id(g_num), g_num))
else:
print("*"*20)
print(g_num)
print("this is father pid = {0}, ppid = {1}, g_num_id = {2}, g_num = {3}".format(os.getpid(), os.getppid(), id(g_num), g_num))
********************
666
this is father pid = 6203, ppid = 6087, g_num_id = 140675625067056, g_num = 666
********************
666
this is son pid = 6941, ppid = 6203, g_num_id = 140675685461968, g_num = 777
multiprocessing
multiprocessing模塊就是跨平臺版本的多進(jìn)程模塊。
multiprocessing模塊提供了一個(gè)Process類來代表一個(gè)進(jìn)程對象
代碼if __name__=='__main__':
在Process使用中必須有
'''Process 創(chuàng)建子進(jìn)程'''
from multiprocessing import Process
import os
# 子進(jìn)程要執(zhí)行的代碼
def run_proc(name):
print('子進(jìn)程運(yùn)行中剔交,name= %s ,pid=%d...' % (name, os.getpid()))
if __name__=='__main__': # 判斷是否在當(dāng)前模塊下
print('父進(jìn)程 %d.' % os.getpid())
p = Process(target=run_proc, args=('test',)) # 創(chuàng)建子進(jìn)程
print('子進(jìn)程將要執(zhí)行')
p.start()
p.join()
print('子進(jìn)程已結(jié)束')
父進(jìn)程 6203.
子進(jìn)程將要執(zhí)行
子進(jìn)程運(yùn)行中肆饶,name= test ,pid=7030...
子進(jìn)程已結(jié)束
'''
對例子的說明:
創(chuàng)建子進(jìn)程時(shí),只需要傳入一個(gè)執(zhí)行函數(shù)和函數(shù)的參數(shù)岖常,
創(chuàng)建一個(gè)Process實(shí)例驯镊,用start()方法啟動,這樣創(chuàng)建進(jìn)程比fork()還要簡單竭鞍。
join()方法可以等待子進(jìn)程結(jié)束后再繼續(xù)往下運(yùn)行板惑,通常用于進(jìn)程間的同步。
'''
Process
'''
Process 語法:
Process([group[, target[, name[, args[, kwargs]]]]])
參數(shù)解釋:
[
group: 線程組偎快,目前還沒有實(shí)現(xiàn)冯乘,庫引用中提示必須是None;
target: 要執(zhí)行的方法晒夹; 進(jìn)程實(shí)例所調(diào)用對象
name: 進(jìn)程名裆馒; 當(dāng)前進(jìn)程的別名
args/kwargs: 要傳入方法的參數(shù)(參數(shù)元組/參數(shù)字典)。
]
Process類常用方法:
is_alive() 判斷進(jìn)程實(shí)例是否還在執(zhí)行
join([timeout]) 是否等待進(jìn)程實(shí)例執(zhí)行結(jié)束丐怯,timeout為等待超時(shí)時(shí)間毫秒級
start() 啟動進(jìn)程實(shí)例(創(chuàng)建子進(jìn)程)
run() 沒有給定target參數(shù)喷好,對指定對象調(diào)用start()方法時(shí),將執(zhí)行run()方法
terminate() 不管任務(wù)是否完成读跷,立即終止
Process 屬性:
authkey
daemon:守護(hù)進(jìn)程標(biāo)識梗搅,在start()調(diào)用之前可以對其進(jìn)行修改
exitcode:進(jìn)程的退出狀態(tài)碼(進(jìn)程運(yùn)行時(shí)為None,-N表示被信號N結(jié)束)
name:進(jìn)程名
pid:進(jìn)程號
'''
# 例子
from multiprocessing import Process
import os
from time import sleep
# 子進(jìn)程執(zhí)行代碼
def run_proc(name, age, **kwargs):
for i in range(10):
print('子進(jìn)程運(yùn)行中,i = {}, name = {}, age = {}, pid = {}'.format(
i, name, age, os.getpid()
))
print(kwargs)
sleep(0.5) # 掛起0.5s
if __name__=='__main__':
print('父進(jìn)程:',os.getpid())
p = Process(target=run_proc, args=('test',22), kwargs={'num':123456})
print('子進(jìn)程將執(zhí)行')
p.start()
sleep(1) # 掛起1s
p.terminate()
p.join()
print('子進(jìn)程結(jié)束')
父進(jìn)程: 6203
子進(jìn)程將執(zhí)行
子進(jìn)程運(yùn)行中无切,i = 0, name = test, age = 22, pid = 7697
{'num': 123456}
子進(jìn)程運(yùn)行中蟀俊,i = 1, name = test, age = 22, pid = 7697
{'num': 123456}
子進(jìn)程結(jié)束
# process 例子2
from multiprocessing import Process
import time
import os
#兩個(gè)子進(jìn)程將會調(diào)用的兩個(gè)方法
def worker_1(interval):
print('worker_1, 父進(jìn)程<{}>,當(dāng)前進(jìn)程<{}>'.format(os.getppid(), os.getpid()))
t_start = time.time()
time.sleep(interval) # 掛起interval秒
t_end = time.time()
print('worker_1執(zhí)行時(shí)間為{}'.format(t_end-t_start))
def worker_2(interval):
print('worker_2, 父進(jìn)程<{}>订雾,當(dāng)前進(jìn)程<{}>'.format(os.getppid(), os.getpid()))
t_start = time.time()
time.sleep(interval) # 掛起interval秒
t_end = time.time()
print('worker_2執(zhí)行時(shí)間為{}'.format(t_end-t_start))
# 輸出當(dāng)前程序的id
print('進(jìn)程pid:', os.getpid())
# 創(chuàng)建兩個(gè)進(jìn)程對象,target指向進(jìn)程對象要執(zhí)行的方法矛洞,
# args后的元組是要傳遞給work_1()中的interval參數(shù)
# 若不指定name參數(shù)洼哎,默認(rèn)的進(jìn)程對象名稱為Process-N,N為一個(gè)遞增的整數(shù)
p1=Process(target=worker_1,args=(3,))
p2=Process(target=worker_2,name='Dog',args=(1,))
# 使用<進(jìn)程對象.start()>來創(chuàng)建并執(zhí)行一個(gè)子進(jìn)程
# 創(chuàng)建的兩個(gè)進(jìn)程在start后分別執(zhí)行worker_1和worker_2中的代碼
p1.start()
p2.start()
# 同時(shí)父進(jìn)程仍然向下執(zhí)行沼本,若p2進(jìn)程還在執(zhí)行噩峦,將返回Ture
print('p2.isalive=',p2.is_alive())
# 輸出p1和p2進(jìn)程的別名和pid
print('p1.name={}\tp1.pid={}'.format(p1.name, p1.pid))
print('p2.name={}\tp2.pid={}'.format(p2.name, p2.pid))
# join() timeout不設(shè)置,表示父進(jìn)程要在這個(gè)位置等待p1
# 進(jìn)程執(zhí)行完成后再執(zhí)行下面的語句
p1.join(1) # 設(shè)置超時(shí)時(shí)間為1s
print('set timeout=1, p1.is_alive=',p1.is_alive()) # 這里應(yīng)該打印True
# 一般用于進(jìn)程間的數(shù)據(jù)同步抽兆,若不寫這一句识补,下面的is_alive判斷將為True,
# 在shell(cmd)中調(diào)用此程序可以完整的看到這個(gè)過程
p1.join() # 設(shè)置等待子進(jìn)程執(zhí)行完畢再執(zhí)行父進(jìn)程的后續(xù)語句
print('p1.is_alive=',p1.is_alive())
# 等待子進(jìn)程執(zhí)行完畢辫红,此處打印可能有輸出延遲
進(jìn)程pid: 6203
worker_1, 父進(jìn)程<6203>凭涂,當(dāng)前進(jìn)程<8048>
worker_2, 父進(jìn)程<6203>,當(dāng)前進(jìn)程<8049>
p2.isalive= True
p1.name=Process-12 p1.pid=8048
p2.name=Dog p2.pid=8049
worker_2執(zhí)行時(shí)間為1.0010364055633545
set timeout=1, p1.is_alive= True
worker_1執(zhí)行時(shí)間為3.002727746963501
p1.is_alive= False
Process 子類
創(chuàng)建新的進(jìn)程還能夠使用類的方式:
可以自定義一個(gè)類贴妻,繼承Process類切油,
每次實(shí)例化這個(gè)類的時(shí)候,就等同于實(shí)例化一個(gè)進(jìn)程對象
用Process子類來進(jìn)行進(jìn)程的控制可以達(dá)到簡化的效果
# Process 子類 例子
from multiprocessing import Process
import time
import os
# 繼承Process類
class ProcessSon(Process):
# Process類本身有__init__方法名惩,重寫Process的__init__方法澎胡,會有一個(gè)問題
# 并沒有完全初始化一個(gè)Process類,所以不能使用這個(gè)類繼承的一些屬性和方法
# 最好的方法是將繼承類本身傳遞給Process.__init__方法娩鹉,完成對自雷的初始化
def __init__(self, interval):
Process.__init__(self)
self.interval = interval
# 重寫Process 的run()方法
def run(self):
print('子進(jìn)程{}開始執(zhí)行攻谁,父進(jìn)程為{}'.format(os.getpid(), os.getppid()))
t_start = time.time()
time.sleep(self.interval)
t_stop = time.time()
print('進(jìn)程{}執(zhí)行結(jié)束,用時(shí){}'.format(os.getpid(), t_stop-t_start))
if __name__=='__main__': # Process 標(biāo)配
t_start = time.time()
print('當(dāng)前進(jìn)程為:{}'.format(os.getpid()))
p1=ProcessSon(3) # 用子類創(chuàng)建一個(gè)新的進(jìn)程對象
# 對一個(gè)不包含target屬性的Process類執(zhí)行start()方法弯予,會執(zhí)行此類的run()方法戚宦,
# 這里執(zhí)行的是p1.run()[也就是ProcessSon.run()]
p1.start()
p1.join()
t_stop = time.time()
print('進(jìn)程{}執(zhí)行結(jié)束,用時(shí){}\nover'.format(os.getpid(), t_stop-t_start))
當(dāng)前進(jìn)程為:2769
子進(jìn)程25847開始執(zhí)行熙涤,父進(jìn)程為2769
進(jìn)程25847執(zhí)行結(jié)束阁苞,用時(shí)3.0029664039611816
進(jìn)程2769執(zhí)行結(jié)束,用時(shí)3.0183212757110596
over
進(jìn)程池Pool
需要?jiǎng)?chuàng)建的子進(jìn)程數(shù)量不多時(shí)祠挫,可以直接用multiprocessing.Process動態(tài)生成多個(gè)進(jìn)程那槽,但是對于數(shù)目過大時(shí),手動創(chuàng)建工作量太大等舔,可以用multiprocessing模塊中的Pool方法
初始化Pool時(shí)可指定最大進(jìn)程數(shù)骚灸,當(dāng)有新的請求提交到Pool時(shí),在Pool未滿下慌植,會創(chuàng)建新進(jìn)程來執(zhí)行請求甚牲,但若Pool已滿义郑,則請求會等待,直到進(jìn)程池有空位才會創(chuàng)建新的進(jìn)程來執(zhí)行請求
Pool進(jìn)程池并不是進(jìn)程數(shù)越大越好
# Pool 例子
#!/usr/bin/env python3
#-*- coding: utf-8 -*-
from multiprocessing import Pool;
import os
import time
import random
run_start = time.time()
def worker(msg):
t_start = time.time()
print('{}開始執(zhí)行丈钙,進(jìn)程號為:{}'.format(msg, os.getpid()))
# random.random()隨機(jī)生成0-1之間的浮點(diǎn)數(shù)
sleep_num = random.random()*2
time.sleep(sleep_num)
t_stop = time.time()
use_time = t_stop-t_start
print('{}執(zhí)行完畢非驮,用時(shí){}'.format(msg, use_time))
print('start'.center(30, '*'))
po = Pool(3) # 定義一個(gè)進(jìn)程池,設(shè)置最大進(jìn)程數(shù)為3
for i in range(10):
# Pool.apply_async(要調(diào)用的目標(biāo)雏赦,(傳遞給目標(biāo)的參數(shù)元組,))
# 每次循環(huán)將會用空閑出的子進(jìn)程去調(diào)用目標(biāo)
po.apply_async(worker, (i, )) # 運(yùn)行worker函數(shù)劫笙,并且worker傳遞參數(shù)為i
po.close() # 關(guān)閉進(jìn)程池,關(guān)閉后po不再接受新的請求
# 進(jìn)程阻塞星岗,如果不添加此句填大,會出現(xiàn)主進(jìn)程執(zhí)行結(jié)束后直接關(guān)閉,
# 子進(jìn)程無法執(zhí)行
po.join() # 等待po中所有子進(jìn)程執(zhí)行完成俏橘,必須放在close語句后
print('end'.center(30, '*'))
run_stop = time.time()
run_time = run_stop-run_start
print('總用時(shí):',run_time)
************start*************
0開始執(zhí)行允华,進(jìn)程號為:9229
1開始執(zhí)行,進(jìn)程號為:9230
2開始執(zhí)行寥掐,進(jìn)程號為:9231
2執(zhí)行完畢靴寂,用時(shí)0.08111715316772461
3開始執(zhí)行,進(jìn)程號為:9231
3執(zhí)行完畢召耘,用時(shí)0.25066304206848145
4開始執(zhí)行榨汤,進(jìn)程號為:9231
1執(zhí)行完畢,用時(shí)0.7374343872070312
5開始執(zhí)行怎茫,進(jìn)程號為:9230
4執(zhí)行完畢收壕,用時(shí)0.934847354888916
6開始執(zhí)行,進(jìn)程號為:9231
0執(zhí)行完畢轨蛤,用時(shí)1.3051488399505615
7開始執(zhí)行蜜宪,進(jìn)程號為:9229
5執(zhí)行完畢,用時(shí)0.7723855972290039
8開始執(zhí)行祥山,進(jìn)程號為:9230
8執(zhí)行完畢圃验,用時(shí)0.9804284572601318
9開始執(zhí)行,進(jìn)程號為:9230
6執(zhí)行完畢缝呕,用時(shí)1.6846919059753418
7執(zhí)行完畢澳窑,用時(shí)1.6566157341003418
9執(zhí)行完畢,用時(shí)1.085120439529419
*************end**************
總用時(shí): 3.6389713287353516
multiprocessing.Pool常用函數(shù)
apply_async(func[, args[,kwds]])
使用非阻塞方式調(diào)用func(并行執(zhí)行供常,都塞方式必須等待上一個(gè)進(jìn)程退出才能執(zhí)行下一個(gè)進(jìn)程)摊聋,args為傳遞給func的參數(shù)列表,kwds為傳遞給func的關(guān)鍵子參數(shù)列表栈暇;
apply(func[, args[, kwds]])
使用阻塞的方式調(diào)用func
close()
關(guān)閉Pool麻裁,使其不再接受新的任務(wù)
terminate()
強(qiáng)制終止任務(wù),不管是否完成
join()
主進(jìn)程阻塞,等待子進(jìn)程的退出煎源,必須在close()或terminate()后使用
'''apply阻塞式'''
# Pool 例子
#!/usr/bin/env python3
#-*- coding: utf-8 -*-
from multiprocessing import Pool;
import os
import time
import random
run_start = time.time()
def worker(msg):
t_start = time.time()
print('{}開始執(zhí)行色迂,進(jìn)程號為:{}'.format(msg, os.getpid()))
# random.random()隨機(jī)生成0-1之間的浮點(diǎn)數(shù)
sleep_num = random.random()*2
time.sleep(sleep_num)
t_stop = time.time()
use_time = t_stop-t_start
print('{}執(zhí)行完畢,用時(shí){}'.format(msg, use_time))
print('start'.center(30, '*'))
po = Pool(3) # 定義一個(gè)進(jìn)程池手销,設(shè)置最大進(jìn)程數(shù)為3
for i in range(10):
# Pool.apply_async(要調(diào)用的目標(biāo)歇僧,(傳遞給目標(biāo)的參數(shù)元組,))
# 每次循環(huán)將會用空閑出的子進(jìn)程去調(diào)用目標(biāo)
po.apply(worker, (i, )) # 運(yùn)行worker函數(shù),并且worker傳遞參數(shù)為i
po.close() # 關(guān)閉進(jìn)程池锋拖,關(guān)閉后po不在接受新的請求
# 進(jìn)程阻塞馏慨,如果不添加此句,會出現(xiàn)主進(jìn)程執(zhí)行結(jié)束后直接關(guān)閉姑隅,
# 子進(jìn)程無法執(zhí)行
po.join() # 等待po中所有子進(jìn)程執(zhí)行完成,必須放在close語句后
print('end'.center(30, '*'))
run_stop = time.time()
run_time = run_stop-run_start
print('總用時(shí):',run_time)
************start*************
0開始執(zhí)行倔撞,進(jìn)程號為:9181
0執(zhí)行完畢讲仰,用時(shí)0.21720004081726074
1開始執(zhí)行,進(jìn)程號為:9182
1執(zhí)行完畢痪蝇,用時(shí)1.266624927520752
2開始執(zhí)行鄙陡,進(jìn)程號為:9183
2執(zhí)行完畢,用時(shí)0.8020675182342529
3開始執(zhí)行躏啰,進(jìn)程號為:9181
3執(zhí)行完畢趁矾,用時(shí)1.9233331680297852
4開始執(zhí)行,進(jìn)程號為:9182
4執(zhí)行完畢给僵,用時(shí)0.48862147331237793
5開始執(zhí)行毫捣,進(jìn)程號為:9183
5執(zhí)行完畢,用時(shí)0.8913943767547607
6開始執(zhí)行帝际,進(jìn)程號為:9181
6執(zhí)行完畢蔓同,用時(shí)0.03457212448120117
7開始執(zhí)行,進(jìn)程號為:9182
7執(zhí)行完畢蹲诀,用時(shí)0.5286092758178711
8開始執(zhí)行斑粱,進(jìn)程號為:9183
8執(zhí)行完畢,用時(shí)0.452136754989624
9開始執(zhí)行脯爪,進(jìn)程號為:9181
9執(zhí)行完畢则北,用時(shí)1.9772589206695557
*************end**************
總用時(shí): 8.642189741134644
進(jìn)程間的通信Queue
可以使用multiprocessing模塊的Queue實(shí)現(xiàn)多進(jìn)程之間的數(shù)據(jù)傳遞,
Queue本身是一個(gè)消息列隊(duì)程序
# Queue 例子
from multiprocessing import Queue
q = Queue(3) # 初始化一個(gè)Queue對象痕慢,最多接收三條put消息
q.put('msg1')
q.put('msg2')
print(q.full()) # False
q.put('msg3')
print(q.full()) # True
# 消息隊(duì)列已滿尚揣,拋出異常,
# 第一個(gè)try會等待2s后再拋出掖举,
# 第二個(gè)try會立刻拋出異常
try:
q.put('msg4',True, 2)
except:
print('消息隊(duì)列已滿惑艇,現(xiàn)有消息數(shù)量:{}'.format(q.qsize()))
try:
q.put_nowait('msg4')
except:
print('消息隊(duì)列已滿,現(xiàn)有消息數(shù)量:{}'.format(q.qsize()))
# 推薦
# 先判斷消息隊(duì)列是否已滿,再寫入
if not q.full():
q.put_nowait('msg4')
# 讀取消息時(shí)滨巴,先判斷消息隊(duì)列是否為空思灌,再讀取
if not q.empty():
for i in range(q.qsize()):
print(q.get_nowait())
False
True
消息隊(duì)列已滿,現(xiàn)有消息數(shù)量:3
消息隊(duì)列已滿恭取,現(xiàn)有消息數(shù)量:3
msg1
msg2
msg3
Queue使用
初始化Queue()對象時(shí)(如:q = Queue())泰偿,若Queue中未指定值,或?yàn)樨?fù)值蜈垮,表示可接受消息數(shù)列無上限(直到內(nèi)存存滿)
Queue.qsize()
返回當(dāng)前隊(duì)列包含的消息數(shù)量
Queue.empty()
消息隊(duì)列判空耗跛,隊(duì)列為空返回True,隊(duì)列已滿返回False
Queue.full()
消息隊(duì)列判滿攒发,已滿调塌,返回True,否則返回False
Queue.get([block[, timeout]])
獲取隊(duì)列的一條消息惠猿,然后將其從隊(duì)列中移除羔砾,block默認(rèn)值為True;
block參數(shù)設(shè)置:
1. 如果block使用默認(rèn)值偶妖,且沒有設(shè)置timeout(單位秒)姜凄,消息隊(duì)列如果已經(jīng)沒有空間寫入,此時(shí)程序?qū)⒈蛔枞ㄍT趯懭霠顟B(tài))趾访,直到從消息隊(duì)列騰出空間為止态秧,若設(shè)置了timeout,則會等待timeout秒扼鞋,若還沒讀取到任何消息申鱼,拋出”Queue.Empty“異常;
2. 若block值為False云头,消息隊(duì)列為空润讥,會立刻拋出”Queue.Empty“異常
Queue.get_nowait()
相當(dāng)于`Queue.get(False)`
Queue.put(item[, block[, timeout]])
參數(shù)解釋:
[
item:將要寫入消息隊(duì)列的消息
block:阻塞設(shè)置
block使用默認(rèn)值(True),且未設(shè)置timeout(單位秒)盘寡,消息隊(duì)列已滿楚殿,此時(shí)程序?qū)⒈蛔枞钡较㈥?duì)列有空間為止竿痰,若設(shè)置了timeout脆粥,等待超時(shí)后會拋出”Queue.full“異常
block=False,消息隊(duì)列已滿影涉,會立刻拋出”Queue.Full”異常
timeout:超時(shí)時(shí)間
]
Queue.put_nowait(item)
相當(dāng)于Queue.put(item,False)
# Queue 實(shí)例
# 這里注意:py文件名一定不能和Python中的保留名相同变隔,不然會報(bào)錯(cuò)
from multiprocessing import Process, Queue
import os,time, random
# 寫數(shù)據(jù)進(jìn)程執(zhí)行
def write(q):
for value in ['A', 'B', 'C']:
print('put {} to queue'.format(value))
q.put(value)
time.sleep(random.random())
# 讀數(shù)據(jù)操作
def read(q):
while True:
if not q.empty():
value = q.get(True)
print('get {} from queue'.format(value))
time.sleep(random.random())
else:
break
if __name__=='__main__':
# 父進(jìn)程創(chuàng)建Queue,并傳給各個(gè)子進(jìn)程
q = Queue()
pw = Process(target=write, args=(q, ))
pr = Process(target=read, args=(q, ))
# 啟動子進(jìn)程pw蟹倾,寫入消息
pw.start()
# 等待子進(jìn)程pw結(jié)束(加入進(jìn)程阻塞)
pw.join()
# 啟動讀消息子進(jìn)程pr
pr.start()
pr.join(5) # 針對read(q)的while設(shè)置匣缘,防止等待過長
# 設(shè)置超時(shí)時(shí)間
pw.terminate()
print('\n所有數(shù)據(jù)讀寫完成猖闪!')
put A to queue
put B to queue
put C to queue
get A from queue
get B from queue
get C from queue
所有數(shù)據(jù)讀寫完成!
進(jìn)程池中的Queue
Pool中使用Queue:
使用的是multiprocessing.Manager()中的Queue(),不是multiprocessing.Queue() 否則會報(bào)錯(cuò)“RuntimeError: Queue objects should only be shared between processes through inheritance.”
# Pool中使用Queue通信
# pool中使用的是multiprocessing.Manager的Queue
from multiprocessing import Manager, Pool
import os, time, random
def reader(q):
print('reader <{}> run, father is <{}>'.format(os.getpid(), os.getppid()))
for i in range(q.qsize()):
print('reader get msg from Queue', q.get(True))
def writer(q):
print('writer <{}> run, father is <{}>'.format(os.getpid(), os.getppid()))
for i in range(5):
q.put(i)
if __name__=='__main__':
print('pid:{} start'.format(os.getpid()))
q = Manager().Queue() # 消息隊(duì)列初始化使用的是Manager().Queue()
po = Pool()
# 阻塞模式創(chuàng)建進(jìn)程肌厨,在writer()完全執(zhí)行完后再執(zhí)行reader()培慌,
# 免去reader()中的死循環(huán)
po.apply(writer, (q, ))
po.apply(reader, (q, ))
po.close() # 關(guān)閉Pool
po.join() # 進(jìn)程阻塞
print('pid:{} end'.format(os.getpid()))
pid:2126 start
writer <3475> run, father is <2126>
reader <3477> run, father is <2126>
reader get msg from Queue 0
reader get msg from Queue 1
reader get msg from Queue 2
reader get msg from Queue 3
reader get msg from Queue 4
pid:2126 end
# 多進(jìn)程拷貝文件
#-*- coding:utf-8 -*-
from multiprocessing import Pool, Manager
import os, shutil # 剛好和os模塊互補(bǔ)
def cp_file(name, oldFolderName, newFolderName, queue):
'''將舊文件夾中的文件拷貝到新文件夾'''
with open(os.path.join(oldFolderName,name), 'r+', encoding='utf-8') as fr:
content = fr.read()
with open(os.path.join(newFolderName, name), 'w', encoding='utf-8') as fw:
fw.write(content)
queue.put(name)
def main():
# 獲取文件要copy的文件夾的名字
oldFolderName = input('input Folder Name:')
# 新建一個(gè)文件夾
newFolderName = oldFolderName+'-復(fù)件'
# 判斷文件夾是否存在
if os.path.exists(newFolderName):
# 存在就刪除后再新建
shutil.rmtree(newFolderName)
os.mkdir(newFolderName)
# 獲取需要copy的文件夾中的所有文件名
fileNames = os.listdir(oldFolderName)
# 使用多進(jìn)程方式copy文件
pool = Pool(6)
queue = Manager().Queue()
for name in fileNames:
pool.apply_async(cp_file, args=(name, oldFolderName, newFolderName, queue))
num = 0
all_num = len(fileNames)
while num<all_num:
queue.get()
num +=1
copyRate = num/all_num
print('\rcopy的進(jìn)度是{:.2f}%'.format((copyRate*100)), end='')
if __name__=='__main__':
main()
# 僵尸進(jìn)程:父進(jìn)程死了,但是子進(jìn)程沒死柑爸,也叫孤兒進(jìn)程
input Folder Name:multiprocessing_test
copy的進(jìn)度是100.00%
線程
線程是應(yīng)用程序中工作的最小單元吵护。
線程可以被搶占(中斷)
在其他線程正在運(yùn)行時(shí),線程可以暫時(shí)擱置(睡眠)表鳍,這是線程的退讓馅而。
線程的執(zhí)行順序由操作系統(tǒng)的調(diào)度算法決定
線程調(diào)度
線程可以分為:
內(nèi)核線程:由操作系統(tǒng)內(nèi)核創(chuàng)建和撤銷。
用戶線程:不需要內(nèi)核支持而在用戶程序中實(shí)現(xiàn)的線程譬圣。
Python使用線程:
函數(shù):_thread模塊 `_thread.start_new_thread(function, args[, kwargs])`
不建議使用,推薦使用threading
類:Thread類
# 未使用模塊的單線程 例子
import time
def about():
print('我是單線程瓮恭,我就是個(gè)弟弟')
time.sleep(0.5)
if __name__=='__main__':
for i in range(5):
about()
我是單線程,我就是個(gè)弟弟
我是單線程厘熟,我就是個(gè)弟弟
我是單線程屯蹦,我就是個(gè)弟弟
我是單線程,我就是個(gè)弟弟
我是單線程盯漂,我就是個(gè)弟弟
'''
調(diào)用thread模塊的函數(shù)式單線程
import _thread
_thread.start_new_thread(fucntion, args[, kwargs])
參數(shù)說明:
[
funciton 線程調(diào)用函數(shù),
args 傳入被調(diào)用線程函數(shù)的參數(shù)笨农,必須為tuple類型“(1,)”
kwargs 可選參數(shù)就缆,dict類型(關(guān)鍵字參數(shù))
]
'''
# thread 線程例子
import _thread
import time
# 定義調(diào)用函數(shù)
def print_time(threadName, delay):
count = 0
while count<3:
time.sleep(delay)
count +=1
print('線程:{}, 時(shí)間:{}'.format(threadName, time.ctime(time.time())))
# 創(chuàng)建兩個(gè)線程
try:
_thread.start_new_thread(print_time, ('Thread-1', 1))
_thread.start_new_thread(print_time, ('Thread-2', 3))
except Exception:
print("Error:can't start thread" )
線程:Thread-1, 時(shí)間:Fri Jan 11 16:31:51 2019
線程:Thread-1, 時(shí)間:Fri Jan 11 16:31:52 2019
線程:Thread-2, 時(shí)間:Fri Jan 11 16:31:53 2019
線程:Thread-1, 時(shí)間:Fri Jan 11 16:31:53 2019
線程:Thread-2, 時(shí)間:Fri Jan 11 16:31:56 2019
線程:Thread-2, 時(shí)間:Fri Jan 11 16:31:59 2019
多線程
多線程類似于同時(shí)執(zhí)行多個(gè)不同程序,多線程優(yōu)點(diǎn):
(1)易于調(diào)度谒亦。
(2)提高并發(fā)性竭宰。通過線程可方便有效地實(shí)現(xiàn)并發(fā)性。進(jìn)程可創(chuàng)建多個(gè)線程來執(zhí)行同一程序的不同部分份招。
(3)開銷少切揭。創(chuàng)建線程比創(chuàng)建進(jìn)程要快,所需開銷很少
線程模塊
Python3中通過兩個(gè)標(biāo)準(zhǔn)庫_thread(python2中為thread)和threading提供對線程的支持锁摔。
_thread 提供低級別的廓旬、原始的線程以及一個(gè)簡單的鎖。
threading 提供包括_thread提供的方法以及其他方法:
threading.currentThread()
返回當(dāng)前的線程變量
threading.enumerate ()
返回一個(gè)包含正在運(yùn)行的線程的list谐腰,正在運(yùn)行指線程啟動后孕豹、結(jié)束前,不包括啟動前和終止后的線程
threading.activeCount()
返回正在運(yùn)行的線程數(shù)量十气,
和len(threading.enumerate())
作用相同
線程模塊同樣提供Thread類處理線程励背,Thread提供的方法:
run()
用以表示線程活動的方法
start()
啟動線程
join(timeout)
阻塞調(diào)用,直到join()
被中止砸西、正常退出叶眉、拋出異持啡澹或是超時(shí)
isAlive()
判斷線程是否活動 這里isAlive()
=is_alive()
getName()
獲取線程名
setName()
設(shè)置線程名
# threading 多線程 例子
import threading
import time
def about():
print('我是多線程,我就是快')
time.sleep(0.5)
if __name__=='__main__':
for i in range(5):
# 創(chuàng)建新的線程
t = threading.Thread(target=about) # 這里about不要加()
t.start() # 啟動線程
# 快的不要不要的
我是多線程衅疙,我就是快
我是多線程莲趣,我就是快
我是多線程,我就是快
我是多線程炼蛤,我就是快
我是多線程妖爷,我就是快
# 主線程會等待所有子線程結(jié)束后才結(jié)束
import threading
from time import sleep, ctime
def sing():
for i in range(3):
print('singing', i)
sleep(1)
def dance():
for i in range(3):
print('dancing', i)
sleep(1)
if __name__=='__main__':
print('start'.center(30, '*'))
# 創(chuàng)建線程
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=dance)
# 啟動線程
t1.start()
t2.start()
sleep(6) # 不寫此行,會出現(xiàn)歌舞未完晚會先完的笑話
print('end'.center(30, '*'))
************start*************
singing 0
dancing 0
singing 1
dancing 1
singing 2
dancing 2
*************end**************
查看線程數(shù)目
# 查看線程數(shù)目例子
#!/usr/bin/python3
# -*- coding:utf-8 -*-
import threading
from time import sleep, ctime
def sing():
for i in range(3):
print('singing', i)
sleep(1)
def dance():
for i in range(3):
print('dancing', i)
sleep(1)
if __name__=='__main__':
print('start'.center(30, '*'), ctime())
t1 = threading.Thread(target=sing)
t2 = threading.Thread(target=sing)
t1.start()
t2.start()
while threading.activeCount()>1:
print('當(dāng)前線程數(shù)為:', threading.activeCount())
sleep(0.5)
print('end'.center(30, '*'), ctime())
'''這里jupyter notebook運(yùn)行此處代碼會出現(xiàn)死循環(huán)理朋,但是在linux下無此問題絮识,
所以這里不運(yùn)行代碼,用Linux截圖顯示'''
************start************* Tue Mar 26 10:16:08 2019
singing 0
singing當(dāng)前線程數(shù)為: 7
0
當(dāng)前線程數(shù)為: 7
singing 1
singing 1
當(dāng)前線程數(shù)為: 7
當(dāng)前線程數(shù)為: 7
---------------------------------------------------------------------------
KeyboardInterrupt Traceback (most recent call last)
<ipython-input-9-b8fdd2db5e43> in <module>
27 while threading.activeCount()>1:
28 print('當(dāng)前線程數(shù)為:', threading.activeCount())
---> 29 sleep(0.5)
30 print('end'.center(30, '*'), ctime())
31 '''這里jupyter notebook運(yùn)行此處代碼會出現(xiàn)死循環(huán)嗽上,但是在linux下無此問題次舌,
KeyboardInterrupt:
threading 注意點(diǎn)
'''
線程執(zhí)行代碼的封裝:
在使用threading模塊時(shí),定義一個(gè)新的子類兽愤,繼承threading.Thread,
然后重寫run方法
'''
# 線程執(zhí)行代碼的封裝 例子
import threading
import time
class MyThread(threading.Thread): # 繼承threading.Thread
def run(self): # 重寫run()
for i in range(3):
time.sleep(1)
msg = '當(dāng)前線程為:'+self.name+'@'+str(i)
print(msg)
if __name__=='__main__':
mt = MyThread()
mt.start()
當(dāng)前線程為:Thread-7@0
當(dāng)前線程為:Thread-7@1
當(dāng)前線程為:Thread-7@2
'''
例子說明:
python的threading.Thread類有一個(gè)run()方法彼念,用于定義線程的功能函數(shù),
可在自己的線程類中覆蓋該方法浅萧,在創(chuàng)建自己的線程實(shí)例后逐沙,
python會調(diào)用用戶自定義的run()
'''
多線程共享全局變量
在一個(gè)進(jìn)程內(nèi)的所有線程共享全局變量,能夠在不適用其他方式的前提下完成多線程之間的數(shù)據(jù)共享(這點(diǎn)要比多進(jìn)程要好)
缺點(diǎn)就是洼畅,線程是對全局變量隨意修改可能造成多線程之間對全局變量的混亂(即線程非安全)
# 多線程共享全局變量 例子
from threading import Thread
import time
g_num = 50
def work1():
global g_num
for i in range(5):
g_num +=2
print('\nin work1, g_num = {}'.format(g_num))
def work2():
global g_num
for i in range(3):
g_num += 3
print('\nin work2, g_num = {}'.format(g_num))
if __name__=='__main__':
t1 = Thread(target=work1)
t2 = Thread(target=work2)
t1.start()
t2.start()
# 這里會出現(xiàn)全局變量混亂現(xiàn)象
in work1, g_num = 52
in work1, g_num = 54
in work1, g_num = 56
in work1, g_num = 58
in work1, g_num = 60
in work2, g_num = 63
in work2, g_num = 66
in work2, g_num = 69
列表當(dāng)實(shí)參傳遞到線程中
# 例子
from threading import Thread
import time
def work1(nums):
nums.append(44)
print('in work1'.center(30, "*"), nums)
def work2(nums):
# 延時(shí)吩案,保證t1線程執(zhí)行完畢
time.sleep(1)
print('in work2'.center(30, "*"), nums)
g_nums = [11, 22, 33]
t1 = Thread(target=work1, args=(g_nums, ))
t1.start()
t2 = Thread(target=work2, args=(g_nums, ))
t2.start()
***********in work1*********** [11, 22, 33, 44]
***********in work2*********** [11, 22, 33, 44]
進(jìn)程VS線程
進(jìn)程:進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位.
線程:線程是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分派的基本單位,它是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位.線程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源(如程序計(jì)數(shù)器,一組寄存器和棧),但是它可與同屬一個(gè)進(jìn)程的其他的線程共享進(jìn)程所擁有的全部資源.
進(jìn)程可以理解為一臺電腦上可以同時(shí)運(yùn)行多個(gè)QQ
線程可以理解為一個(gè)QQ可以開多個(gè)聊天窗口
進(jìn)程和線程的關(guān)系:
(1)一個(gè)線程只能屬于一個(gè)進(jìn)程,而一個(gè)進(jìn)程可以有多個(gè)線程帝簇,但至少有一個(gè)線程徘郭。
(2)資源分配給進(jìn)程,同一進(jìn)程的所有線程共享該進(jìn)程的所有資源丧肴。
(3)處理機(jī)分給線程残揉,即真正在處理機(jī)上運(yùn)行的是線程
(4)線程在執(zhí)行過程中,需要協(xié)作同步芋浮。不同進(jìn)程的線程間要利用消息通信的辦法實(shí)現(xiàn)同步抱环。線程是指進(jìn)程內(nèi)的一個(gè)執(zhí)行單元,也是進(jìn)程內(nèi)的可調(diào)度實(shí)體.
進(jìn)程與線程的區(qū)別:
(1)調(diào)度:線程作為調(diào)度和分配的基本單位,進(jìn)程作為擁有資源的基本單位
(2)并發(fā)性:不僅進(jìn)程之間可以并發(fā)執(zhí)行纸巷,同一個(gè)進(jìn)程的多個(gè)線程之間也可并發(fā)執(zhí)行
(3)擁有資源:進(jìn)程是擁有資源的一個(gè)獨(dú)立單位江醇,線程不擁有系統(tǒng)資源,但可以訪問隸屬于進(jìn)程的資源.
(4)系統(tǒng)開銷:在創(chuàng)建或撤消進(jìn)程時(shí)何暇,由于系統(tǒng)都要為之分配和回收資源陶夜,導(dǎo)致系統(tǒng)的開銷明顯大于創(chuàng)建或撤消線程時(shí)的開銷。
各自優(yōu)缺點(diǎn)
線程和進(jìn)程在使用上各有優(yōu)缺點(diǎn):線程執(zhí)行開銷小裆站,但不利于資源的管理和保護(hù)条辟;而進(jìn)程正相反黔夭。
線程同步
線程同步能夠保證多個(gè)線程安全訪問競爭資源,最簡單的同步機(jī)制是引入互斥鎖羽嫡。
互斥鎖為資源引入一個(gè)狀態(tài):鎖定/非鎖定本姥。
某個(gè)線程要更改共享數(shù)據(jù)時(shí),先將其鎖定杭棵,此時(shí)資源的狀態(tài)為“鎖定”婚惫,其他線程不能更改;直到該線程釋放資源魂爪,將資源的狀態(tài)變成“非鎖定”先舷,其他的線程才能再次鎖定該資源∽沂蹋互斥鎖保證了每次只有一個(gè)線程進(jìn)行寫入操作蒋川,從而保證了多線程情況下數(shù)據(jù)的正確性。
使用 Thread 對象的 Lock 和 Rlock 可以實(shí)現(xiàn)簡單的線程同步撩笆,這兩個(gè)對象都有 acquire 方法和 release 方法捺球,對于那些需要每次只允許一個(gè)線程操作的數(shù)據(jù),可以將其操作放到 acquire 和 release 方法之間夕冲。
# 線程不安全 例子
from threading import Thread
import time
g_num = 0
g_number = 0
def test1():
global g_num
for i in range(1000000):
g_num +=1
print('in test1, g_num = {}'.format(g_num))
def test2():
global g_num
for i in range(1000000):
g_num +=1
print('***in test2, g_num = {}'.format(g_num))
def test3():
global g_number
for i in range(1000000):
g_number +=1
print('***in test3, g_number = {}'.format(g_number))
def test4():
global g_number
for i in range(1000000):
g_number +=1
print('***in test4, g_number = {}'.format(g_number))
# 未對線程運(yùn)行進(jìn)行調(diào)節(jié)
t1 = Thread(target=test1)
t1.start()
t2 = Thread(target=test2)
t2.start()
print('g_num = {}'.format(g_num))
time.sleep(5) # 保證線程t1氮兵、t2執(zhí)行完畢
# 對線程運(yùn)行進(jìn)行調(diào)節(jié)
t3 = Thread(target=test3)
t3.start()
time.sleep(5) # 睡眠5s,保證線程t3可以完全執(zhí)行完畢
t4 = Thread(target=test4)
t4.start()
t4.join()# 線程阻塞
print('g_number = {}'.format(g_number))
g_num = 362029
in test1, g_num = 1057030
***in test2, g_num = 1584212
***in test3, g_number = 1000000
***in test4, g_number = 2000000
g_number = 2000000
互斥鎖
當(dāng)多個(gè)線程幾乎同時(shí)修改某一個(gè)共享數(shù)據(jù)的時(shí)候歹鱼,需要進(jìn)行同步控制
線程同步能夠保證多個(gè)線程安全訪問競爭資源泣栈,最簡單的同步機(jī)制是引入互斥鎖。
互斥鎖為資源引入一個(gè)狀態(tài):鎖定/非鎖定醉冤。
添加鎖的原則是:在保證程序正確的前提下秩霍,盡可能的少鎖住代碼篙悯。(這樣鎖死的時(shí)間就越少)
threading模塊中的Lock類蚁阳,可以方便的處理鎖定:
創(chuàng)建鎖:
mutex = threading.Lock()
鎖定:
mutex.acquire([blocking])
blocking參數(shù)解釋:
若設(shè)定blocking為True,則當(dāng)前線程堵塞鸽照,直到獲得這個(gè)鎖為止(默認(rèn)為True)
若blocking設(shè)置為False螺捐,則當(dāng)前線程不會堵塞
釋放:
mutex.release()
# 用互斥鎖實(shí)現(xiàn)上例的進(jìn)程同步 例子
from threading import Thread, Lock
from time import sleep
g_num = 0
def test1():
global g_num
for i in range(100000):
# True表示阻塞,即若這個(gè)鎖在要上鎖之前已經(jīng)被上鎖矮燎,那么此線程會一直等待到解鎖
# False表示非阻塞定血,即不管本次上鎖能否成功,都不會卡在這里诞外,會繼續(xù)執(zhí)行下面的代碼
mutexFlag = mutex.acquire(True)
if mutexFlag:
g_num +=1
mutex.release()
print('in test1, g_num={}'.format(g_num))
def test2():
global g_num
for i in range(100000):
# True表示阻塞澜沟,即若這個(gè)鎖在要上鎖之前已經(jīng)被上鎖,那么此線程會一直等待到解鎖
# False表示非阻塞峡谊,即不管本次上鎖能否成功茫虽,都不會卡在這里,會繼續(xù)執(zhí)行下面的代碼
mutexFlag = mutex.acquire(True)
if mutexFlag:
g_num +=1
mutex.release()
print('**in test2, g_num={}'.format(g_num))
# 創(chuàng)建一個(gè)互斥鎖,這個(gè)鎖默認(rèn)是未上鎖狀態(tài)
mutex = Lock()
t1 = Thread(target=test1)
t1.start()
t2 = Thread(target=test2)
t2.start()
print('for total g_num = {}'.format(g_num))
for total g_num = 11476
in test1, g_num=190843
**in test2, g_num=200000
'''
上鎖解鎖過程
當(dāng)一個(gè)線程調(diào)用Lock的acquire()獲得鎖時(shí)占调,鎖就進(jìn)入“l(fā)ocked’狀態(tài)
每次只有一個(gè)線程可以獲得鎖蝌焚,如果此時(shí)另一個(gè)線程試圖獲得此鎖,
嘗試獲取鎖線程會變?yōu)椤眀locked“狀態(tài)号杏,稱為”阻塞“
直到擁有鎖的線程調(diào)用鎖的release()方法釋放鎖婴氮,鎖進(jìn)入”unlocked“狀態(tài)
線程調(diào)度程序從處于同步阻塞狀態(tài)的線程中選擇一個(gè)來獲得鎖,
并使得該線程進(jìn)入運(yùn)行(running)狀態(tài)盾致。
'''
'''
鎖的好處:
確保了某段關(guān)鍵代碼只能由一個(gè)線程從頭到尾完整地執(zhí)行
鎖的壞處:
阻止了多線程并發(fā)執(zhí)行主经,包含鎖的某段代碼實(shí)際上只能以單線程模式執(zhí)行,
效率就大大地下降了
由于可以存在多個(gè)鎖绰上,不同的線程持有不同的鎖旨怠,
并試圖獲取對方持有的鎖時(shí),可能會造成死鎖
'''
'''
對多線程-非共享數(shù)據(jù)(非全局變量):
在多線程開發(fā)中蜈块,全局變量是多個(gè)線程都共享的數(shù)據(jù)鉴腻,
而局部變量等是各自線程的,是非共享的
'''
死鎖
在線程間共享多個(gè)資源的時(shí)候百揭,如果兩個(gè)線程分別占有一部分資源并且同時(shí)等待對方的資源爽哎,就會造成死鎖。
避免死鎖
程序設(shè)計(jì)時(shí)要盡量避免(銀行家算法)
添加超時(shí)時(shí)間等
銀行家算法
一個(gè)銀行家擁有一定數(shù)量的資金器一,有若干個(gè)客戶要貸款课锌。每個(gè)客戶須在一開始就聲明他所需貸款的總額。若該客戶貸款總額不超過銀行家的資金總數(shù)祈秕,銀行家可以接收客戶的要求渺贤。客戶貸款是以每次一個(gè)資金單位(如1萬RMB等)的方式進(jìn)行的请毛,客戶在借滿所需的全部單位款額之前可能會等待志鞍,但銀行家須保證這種等待是有限的,可完成的方仿。
例如:有三個(gè)客戶C1固棚,C2,C3仙蚜,向銀行家借款此洲,該銀行家的資金總額為10個(gè)資金單位,其中C1客戶要借9各資金單位委粉,C2客戶要借3個(gè)資金單位呜师,C3客戶要借8個(gè)資金單位,總計(jì)20個(gè)資金單位贾节。某一時(shí)刻的狀態(tài)如圖所示汁汗。
對于a圖的狀態(tài)趟紊,按照安全序列的要求,我們選的第一個(gè)客戶應(yīng)滿足該客戶所需的貸款小于等于銀行家當(dāng)前所剩余的錢款碰酝,可以看出只有C2客戶能被滿足:C2客戶需1個(gè)資金單位霎匈,小銀行家手中的2個(gè)資金單位,于是銀行家把1個(gè)資金單位借給C2客戶送爸,使之完成工作并歸還所借的3個(gè)資金單位的錢铛嘱,進(jìn)入b圖。同理袭厂,銀行家把4個(gè)資金單位借給C3客戶墨吓,使其完成工作,在c圖中纹磺,只剩一個(gè)客戶C1帖烘,它需7個(gè)資金單位,這時(shí)銀行家有8個(gè)資金單位橄杨,所以C1也能順利借到錢并完成工作秘症。最后(見圖d)銀行家收回全部10個(gè)資金單位,保證不賠本式矫。那麼客戶序列{C1乡摹,C2,C3}就是個(gè)安全序列采转,按照這個(gè)序列貸款聪廉,銀行家才是安全的。否則的話故慈,若在圖b狀態(tài)時(shí)板熊,銀行家把手中的4個(gè)資金單位借給了C1,則出現(xiàn)不安全狀態(tài):這時(shí)C1察绷,C3均不能完成工作干签,而銀行家手中又沒有錢了,系統(tǒng)陷入僵持局面克婶,銀行家也不能收回投資筒严。
綜上所述丹泉,銀行家算法是從當(dāng)前狀態(tài)出發(fā)情萤,逐個(gè)按安全序列檢查各客戶誰能完成其工作,然后假定其完成工作且歸還全部貸款摹恨,再進(jìn)而檢查下一個(gè)能完成工作的客戶筋岛,......。如果所有客戶都能完成工作晒哄,則找到一個(gè)安全序列睁宰,銀行家才是安全的肪获。
'''同步應(yīng)用'''
# 使用互斥鎖完成多個(gè)任務(wù),有序的進(jìn)行工作
import queue
from threading import Thread, Lock
from time import sleep
class Task1(Thread):
def run(self):
while True:
if lock1.acquire():
print('Task 1'.center(30, "*"))
q.put(1)
if q.full():
break
sleep(0.5)
lock2.release()
class Task2(Thread):
def run(self):
while True:
if lock2.acquire():
print('Task 2'.center(30, "*"))
q.put(1)
if q.full():
break
sleep(0.5)
lock3.release()
class Task3(Thread):
def run(self):
while True:
if lock3.acquire():
print('Task 3'.center(30, "*"))
q.put(1)
if q.full():
break
sleep(0.5)
lock1.release()
q = queue.Queue(9) # 消息隊(duì)列柒傻,控制線程循環(huán)
# 使用Lock創(chuàng)建出的鎖默認(rèn)不上鎖
lock1 = Lock()
# 創(chuàng)建另一把鎖孝赫,并“鎖上“
lock2 = Lock()
lock2.acquire()
# 創(chuàng)建另一把鎖,并“鎖上“
lock3 = Lock()
lock3.acquire()
t1 = Task1()
t2 = Task2()
t3 = Task3()
t1.start()
t2.start()
t3.start()
if q.full():
t1.terminate()
t2.terminate()
t3.terminate()
************Task 1************
************Task 2************
************Task 3************
************Task 1************
************Task 2************
************Task 3************
************Task 1************
************Task 2************
************Task 3************
生產(chǎn)者消費(fèi)者模式
隊(duì)列
先進(jìn)先出
棧
后進(jìn)先出
線程優(yōu)先級隊(duì)列( Queue):
Python 的 Queue 模塊中提供了同步的红符、線程安全的隊(duì)列類青柄,
包括FIFO(先入先出)隊(duì)列Queue,LIFO(后入先出)隊(duì)列LifoQueue预侯,和優(yōu)先級隊(duì)列 PriorityQueue
這些隊(duì)列都實(shí)現(xiàn)了鎖原語(可以理解為原子操作致开,即要么不做,要么就做完)萎馅,
能夠在多線程中直接使用双戳。可以使用隊(duì)列來實(shí)現(xiàn)線程間的同步糜芳。
import queue
q = queue.Queue()
這里的Queue和進(jìn)程里Queue的方法和屬性功能相同飒货,不再贅述
Queue.task_done()
在完成一項(xiàng)工作之后,Queue.task_done()函數(shù)向任務(wù)已經(jīng)完成的隊(duì)列發(fā)送一個(gè)信號
'''
# 用FIFO隊(duì)列實(shí)現(xiàn)生成者消費(fèi)者問題 例子:
'''
例子沒有結(jié)束控制峭竣,會一直循環(huán)膏斤,不建議直接運(yùn)行
'''
import threading
import time
from queue import Queue
# 在py2中導(dǎo)入Queue方法:
# from Queue import Queue
class Producer(threading.Thread):
def run(self):
global queue
count = 0
while True:
if(queue.qsize()<1000):
for i in range(100):
count +=1
msg = '生成產(chǎn)品' + str(count)
queue.put(msg)
print(msg)
time.sleep(0.5)
class Consumer(threading.Thread):
def run(self):
global queue
while True:
if queue.qsize() > 100:
for i in range(3):
msg = self.name + '消費(fèi)了' + queue.get()
print(msg)
time.sleep(1)
if __name__=='__main__':
queue=Queue()
for i in range(500):
queue.put('初始產(chǎn)品'+str(i))
for i in range(2):
p = Producer()
p.start()
for i in range(5):
c = Consumer()
c.start()
生成產(chǎn)品1
生成產(chǎn)品2
生成產(chǎn)品3
生成產(chǎn)品4
生成產(chǎn)品5
生成產(chǎn)品6
生成產(chǎn)品7
生成產(chǎn)品8
生成產(chǎn)品9
生成產(chǎn)品10
生成產(chǎn)品11
生成產(chǎn)品12
生成產(chǎn)品13
生成產(chǎn)品14
生成產(chǎn)品15
生成產(chǎn)品16
生成產(chǎn)品17
生成產(chǎn)品18
生成產(chǎn)品19
生成產(chǎn)品20
生成產(chǎn)品21
生成產(chǎn)品22
生成產(chǎn)品23
生成產(chǎn)品24
生成產(chǎn)品25
生成產(chǎn)品26
生成產(chǎn)品27
生成產(chǎn)品28
生成產(chǎn)品29
生成產(chǎn)品30
生成產(chǎn)品31
生成產(chǎn)品32
生成產(chǎn)品33
生成產(chǎn)品34
生成產(chǎn)品35
生成產(chǎn)品36
生成產(chǎn)品37
生成產(chǎn)品38
生成產(chǎn)品39
生成產(chǎn)品40
生成產(chǎn)品41
生成產(chǎn)品42
生成產(chǎn)品43
生成產(chǎn)品44
生成產(chǎn)品45
生成產(chǎn)品46
生成產(chǎn)品47
生成產(chǎn)品48
生成產(chǎn)品49
生成產(chǎn)品50
生成產(chǎn)品51
生成產(chǎn)品52
生成產(chǎn)品53
生成產(chǎn)品54
生成產(chǎn)品55
生成產(chǎn)品56
生成產(chǎn)品57
生成產(chǎn)品58
生成產(chǎn)品59
生成產(chǎn)品60
生成產(chǎn)品61
生成產(chǎn)品62
生成產(chǎn)品63
生成產(chǎn)品64
生成產(chǎn)品65
生成產(chǎn)品66
生成產(chǎn)品67
生成產(chǎn)品68
生成產(chǎn)品69
生成產(chǎn)品70
生成產(chǎn)品71
生成產(chǎn)品72
生成產(chǎn)品73
生成產(chǎn)品74
生成產(chǎn)品75
生成產(chǎn)品76
生成產(chǎn)品77
生成產(chǎn)品78
生成產(chǎn)品79
生成產(chǎn)品80
生成產(chǎn)品81
生成產(chǎn)品82
生成產(chǎn)品83
生成產(chǎn)品84
生成產(chǎn)品85
生成產(chǎn)品86
生成產(chǎn)品87
生成產(chǎn)品88
生成產(chǎn)品89
生成產(chǎn)品90
生成產(chǎn)品91
生成產(chǎn)品92
生成產(chǎn)品93
生成產(chǎn)品94
生成產(chǎn)品95
生成產(chǎn)品96
生成產(chǎn)品97
生成產(chǎn)品98
生成產(chǎn)品99
生成產(chǎn)品100
生成產(chǎn)品1
生成產(chǎn)品2
生成產(chǎn)品3
生成產(chǎn)品4
生成產(chǎn)品5
生成產(chǎn)品6
生成產(chǎn)品7
生成產(chǎn)品8
生成產(chǎn)品9
生成產(chǎn)品10
生成產(chǎn)品11
生成產(chǎn)品12
生成產(chǎn)品13
生成產(chǎn)品14
生成產(chǎn)品15
生成產(chǎn)品16
生成產(chǎn)品17
生成產(chǎn)品18
生成產(chǎn)品19
生成產(chǎn)品20
生成產(chǎn)品21
生成產(chǎn)品22
生成產(chǎn)品23
生成產(chǎn)品24
生成產(chǎn)品25
Thread-6消費(fèi)了初始產(chǎn)品0
Thread-6消費(fèi)了初始產(chǎn)品1
Thread-6消費(fèi)了初始產(chǎn)品2
生成產(chǎn)品26
生成產(chǎn)品27
生成產(chǎn)品28
生成產(chǎn)品29
生成產(chǎn)品30
生成產(chǎn)品31
生成產(chǎn)品32
生成產(chǎn)品33
生成產(chǎn)品34
生成產(chǎn)品35
生成產(chǎn)品36Thread-7消費(fèi)了初始產(chǎn)品3
Thread-7消費(fèi)了初始產(chǎn)品4
Thread-7消費(fèi)了初始產(chǎn)品5
Thread-8消費(fèi)了初始產(chǎn)品6
Thread-8消費(fèi)了初始產(chǎn)品7
Thread-8消費(fèi)了初始產(chǎn)品8
生成產(chǎn)品37
生成產(chǎn)品38
生成產(chǎn)品39
生成產(chǎn)品40
生成產(chǎn)品41
Thread-9消費(fèi)了初始產(chǎn)品9
Thread-9消費(fèi)了初始產(chǎn)品10
Thread-9消費(fèi)了初始產(chǎn)品11
Thread-10消費(fèi)了初始產(chǎn)品12
Thread-10消費(fèi)了初始產(chǎn)品13
Thread-10消費(fèi)了初始產(chǎn)品14
生成產(chǎn)品42
生成產(chǎn)品43
生成產(chǎn)品44
生成產(chǎn)品45
生成產(chǎn)品46
生成產(chǎn)品47
生成產(chǎn)品48
生成產(chǎn)品49
生成產(chǎn)品50
生成產(chǎn)品51
生成產(chǎn)品52
生成產(chǎn)品53
生成產(chǎn)品54
生成產(chǎn)品55
生成產(chǎn)品56
生成產(chǎn)品57
生成產(chǎn)品58
生成產(chǎn)品59
生成產(chǎn)品60
生成產(chǎn)品61
生成產(chǎn)品62
生成產(chǎn)品63
生成產(chǎn)品64
生成產(chǎn)品65
生成產(chǎn)品66
生成產(chǎn)品67
生成產(chǎn)品68
生成產(chǎn)品69
生成產(chǎn)品70
生成產(chǎn)品71
生成產(chǎn)品72
生成產(chǎn)品73
生成產(chǎn)品74
生成產(chǎn)品75
生成產(chǎn)品76
生成產(chǎn)品77
生成產(chǎn)品78
生成產(chǎn)品79
生成產(chǎn)品80
生成產(chǎn)品81
生成產(chǎn)品82
生成產(chǎn)品83
生成產(chǎn)品84
生成產(chǎn)品85
生成產(chǎn)品86
生成產(chǎn)品87
生成產(chǎn)品88
生成產(chǎn)品89
生成產(chǎn)品90
生成產(chǎn)品91
生成產(chǎn)品92
生成產(chǎn)品93
生成產(chǎn)品94
生成產(chǎn)品95
生成產(chǎn)品96
生成產(chǎn)品97
生成產(chǎn)品98
生成產(chǎn)品99
生成產(chǎn)品100
ThreadLocal
多線程環(huán)境下,每個(gè)線程都有自己的數(shù)據(jù)邪驮。一個(gè)線程使用自己的局部變量比使用全局變量好莫辨,因?yàn)榫植孔兞恐挥芯€程自己能看見,不會影響其他線程毅访,而全局變量的修改必須加鎖沮榜。
'''
使用函數(shù)傳參的方法
每層都要傳入?yún)?shù),啰嗦的很
'''
def process_student(name):
std = Student(name)
# std是局部變量喻粹,但是每個(gè)函數(shù)都要用它蟆融,因此必須傳進(jìn)去:
do_task_1(std)
do_task_2(std)
def do_task_1(std):
do_subtask_1(std)
do_subtask_2(std)
def do_task_2(std):
do_subtask_2(std)
do_subtask_2(std)
'''
使用全局字典的方法
這種方式理論上是可行的,
它最大的優(yōu)點(diǎn)是消除了std對象在每層函數(shù)中的傳遞問題守呜,
但是型酥,每個(gè)函數(shù)獲取std的代碼有點(diǎn)low。
'''
global_dict = {}
def std_thread(name):
std = Student(name)
# 把std放到全局變量global_dict中:
global_dict[threading.current_thread()] = std
do_task_1()
do_task_2()
def do_task_1():
# 不傳入std查乒,而是根據(jù)當(dāng)前線程查找:
std = global_dict[threading.current_thread()]
...
def do_task_2():
# 任何函數(shù)都可以查找出當(dāng)前線程的std變量:
std = global_dict[threading.current_thread()]
...
使用ThreadLocal
threadlocal是用于解決多線程之間的共享數(shù)據(jù)的參數(shù)紊亂問題
import threading
# 用threading.local() 生成全局變量
global_var = threading.local()
ThreadLocal最常用的地方就是為每個(gè)線程綁定一個(gè)數(shù)據(jù)庫連接弥喉,HTTP請求,用戶身份信息等玛迄,這樣一個(gè)線程的所有調(diào)用到的處理函數(shù)都可以非常方便地訪問這些資源由境。
一個(gè)ThreadLocal變量雖然是全局變量,但每個(gè)線程都只能讀寫自己線程的獨(dú)立副本,互不干擾虏杰。ThreadLocal解決了參數(shù)在一個(gè)線程中各個(gè)函數(shù)之間互相傳遞的問題
# ThreadLocal 例子
import threading
# 創(chuàng)建全局ThreadLocal對象
local_school = threading.local()
def process_student():
# 獲取當(dāng)前線程相關(guān)的student
std = local_school.student
print('hello,{}(in {})'.format(std, threading.current_thread().name))
def process_thread(name):
# 綁定ThreadLocal的student
local_school.student = name
process_student()
t1 = threading.Thread(target=process_thread, args=('zhangsan', ), name='Thread_A')
t2 = threading.Thread(target=process_thread, args=('lisi', ), name='Thread_B')
t1.start()
t2.start()
t1.join()
t2.join()
# 全局變量local_school就是一個(gè)ThreadLocal對象讥蟆,
# 每個(gè)Thread對它都可以讀寫student屬性,但互不影響纺阔。
# 你可以把local_school看成全局變量瘸彤,
# 但每個(gè)屬性如local_school.student都是線程的局部變量,
# 可以任意讀寫而互不干擾笛钝,也不用管理鎖的問題钧栖,ThreadLocal內(nèi)部會處理。
# 可以理解為全局變量local_school是一個(gè)dict婆翔,
# 不但可以用local_school.student拯杠,還可以綁定其他變量,
# 如local_school.teacher等等啃奴。
hello,zhangsan(in Thread_A)
hello,lisi(in Thread_B)
異步
老張愛喝茶潭陪,廢話不說,煮開水最蕾。
出場人物:老張依溯,水壺兩把(普通水壺,簡稱水壺瘟则;會響的水壺黎炉,簡稱響水壺)。
1 老張把水壺放到火上醋拧,立等水開慷嗜。(同步阻塞)
老張覺得自己有點(diǎn)傻
2 老張把水壺放到火上,去客廳看電視丹壕,時(shí)不時(shí)去廚房看看水開沒有庆械。(同步非阻塞)
老張還是覺得自己有點(diǎn)傻,于是變高端了菌赖,買了把會響笛的那種水壺缭乘。水開之后,能大聲發(fā)出嘀嘀的噪音琉用。
3 老張把響水壺放到火上堕绩,立等水開。(異步阻塞)
老張覺得這樣傻等意義不大
4 老張把響水壺放到火上邑时,去客廳看電視奴紧,水壺響之前不再去看它了,響了再去拿壺刁愿。(異步非阻塞)
老張覺得自己聰明了绰寞。
所謂同步異步到逊,只是對于水壺而言铣口。
普通水壺滤钱,同步;響水壺脑题,異步件缸。
雖然都能干活,但響水壺可以在自己完工之后叔遂,提示老張水開了他炊。這是普通水壺所不能及的。
同步只能讓調(diào)用者去輪詢自己(情況2中)已艰,造成老張效率的低下痊末。
所謂阻塞非阻塞,僅僅對于老張而言哩掺。
立等的老張凿叠,阻塞;看電視的老張嚼吞,非阻塞盒件。
情況1和情況3中老張就是阻塞的,媳婦喊他都不知道舱禽。雖然3中響水壺是異步的炒刁,可對于立等的老張沒有太大的意義。所以一般異步是配合非阻塞使用的誊稚,這樣才能發(fā)揮異步的效用翔始。
# 異步例子
from multiprocessing import Pool
import time
import os
def test_1():
print('進(jìn)程池中的進(jìn)程:pid = {}, ppid = {}'.format(os.getpid(), os.getppid()))
for i in range(3):
print('{}'.format(i).center(10, '*'))
time.sleep(0.5)
return 'function test_1'
def test_2(args):
print('callback func pid = {}'.format(os.getpid()))
print('callback func args = {}'.format(args))
pool = Pool(3)
pool.apply_async(func=test_1, callback=test_2)
time.sleep(3)
print('主進(jìn)程:pid = {}'.format(os.getpid()))
進(jìn)程池中的進(jìn)程:pid = 3912, ppid = 3897
****0*****
****1*****
****2*****
callback func pid = 3897
callback func args = function test_1
主進(jìn)程:pid = 3897
GIL(全局解釋鎖)
這里特別注意:在python中多進(jìn)程比多線程的效率要高
#加載動態(tài)庫
from ctypes import *
lib = cdll.LoadLibrary("loadName") #這里是用于導(dǎo)入庫
分布式進(jìn)程
在Thread和Process中,應(yīng)當(dāng)優(yōu)選Process里伯,因?yàn)镻rocess更穩(wěn)定绽昏,而且,Process可以分布到多臺機(jī)器上俏脊,而Thread最多只能分布到同一臺機(jī)器的多個(gè)CPU上全谤。
原有的Queue可以繼續(xù)使用,但是爷贫,通過managers模塊把Queue通過網(wǎng)絡(luò)暴露出去认然,就可以讓其他機(jī)器的進(jìn)程訪問Queue了。
# task_master.py
import random, time, queue
from multiprocessing.managers import BaseManager
# 發(fā)送任務(wù)的隊(duì)列:
task_queue = queue.Queue()
# 接收結(jié)果的隊(duì)列:
result_queue = queue.Queue()
# 從BaseManager繼承的QueueManager:
class QueueManager(BaseManager):
pass
# 把兩個(gè)Queue都注冊到網(wǎng)絡(luò)上, callable參數(shù)關(guān)聯(lián)了Queue對象:
QueueManager.register('get_task_queue', callable=lambda: task_queue)
QueueManager.register('get_result_queue', callable=lambda: result_queue)
# 綁定端口5000, 設(shè)置驗(yàn)證碼'abc':
manager = QueueManager(address=('', 5000), authkey=b'abc')
# 啟動Queue:
manager.start()
# 獲得通過網(wǎng)絡(luò)訪問的Queue對象:
task = manager.get_task_queue()
result = manager.get_result_queue()
# 放幾個(gè)任務(wù)進(jìn)去:
for i in range(10):
n = random.randint(0, 10000)
print('Put task %d...' % n)
task.put(n)
# 從result隊(duì)列讀取結(jié)果:
print('Try get results...')
for i in range(10):
r = result.get(timeout=10)
print('Result: %s' % r)
# 關(guān)閉:
manager.shutdown()
print('master exit.')
# 當(dāng)我們在一臺機(jī)器上寫多進(jìn)程程序時(shí)漫萄,創(chuàng)建的Queue可以直接拿來用卷员,
# 但是,在分布式多進(jìn)程環(huán)境下腾务,
# 添加任務(wù)到Queue不可以直接對原始的task_queue進(jìn)行操作毕骡,
# 那樣就繞過了QueueManager的封裝,
# 必須通過manager.get_task_queue()獲得的Queue接口添加。
# task_worker.py
import time, sys, queue
from multiprocessing.managers import BaseManager
# 創(chuàng)建類似的QueueManager:
class QueueManager(BaseManager):
pass
# 由于這個(gè)QueueManager只從網(wǎng)絡(luò)上獲取Queue未巫,所以注冊時(shí)只提供名字:
QueueManager.register('get_task_queue')
QueueManager.register('get_result_queue')
# 連接到服務(wù)器窿撬,也就是運(yùn)行task_master.py的機(jī)器:
server_addr = '127.0.0.1'
print('Connect to server %s...' % server_addr)
# 端口和驗(yàn)證碼注意保持與task_master.py設(shè)置的完全一致:
m = QueueManager(address=(server_addr, 5000), authkey=b'abc')
# 從網(wǎng)絡(luò)連接:
m.connect()
# 獲取Queue的對象:
task = m.get_task_queue()
result = m.get_result_queue()
# 從task隊(duì)列取任務(wù),并把結(jié)果寫入result隊(duì)列:
for i in range(10):
try:
n = task.get(timeout=1)
print('run task %d * %d...' % (n, n))
r = '%d * %d = %d' % (n, n, n*n)
time.sleep(1)
result.put(r)
except Queue.Empty:
print('task queue is empty.')
# 處理結(jié)束:
print('worker exit.')
異步IO
為解決CPU高速執(zhí)行能力和IO設(shè)備的龜速嚴(yán)重不匹配,提供了多線程和多進(jìn)程方法叙凡,但是還有一種解決方法:異步IO劈伴。(當(dāng)代碼需要執(zhí)行一個(gè)耗時(shí)的IO操作時(shí),它只發(fā)出IO指令握爷,并不等待IO結(jié)果跛璧,然后就去執(zhí)行其他代碼了。一段時(shí)間后新啼,當(dāng)IO返回結(jié)果時(shí)追城,再通知CPU進(jìn)行處理。)
異步IO模型需要一個(gè)消息循環(huán)燥撞,在消息循環(huán)中漓柑,主線程不斷地重復(fù)“讀取消息-處理消息”這一過程:
loop = get_event_loop()
while True:
event = loop.get_event()
process_event(event)