引言
在多線程編程出現之前,電腦程序的運行由一個執行序列組成,執行序列按順序在主機的CPU中運行。無論是任務本身要求順序執行還是整個程序是由多個子任務組成,程序都是按這種方式執行的。即使子任務相當獨立,相互無關(即,一個子任務的結果不影響其他子任務的結果)。這樣并行處理可以大幅度地提升整個任務的效率,這也就是多線程編程的目的。
什么是線程
線程(有時被稱為輕量級進程)跟進程有些相似,不同的是,所有的線程運行在同一個進程,共享相同的運行環境。
線程有開始,順序執行和結束三部分。它有自己的指令指針,記錄自己運行到什么地方。線程的運行可能被搶占(中斷),或暫時掛起(也叫睡眠),讓其他的線程運行(也叫讓步)。一個進程中的各個進程之前共享同一片數據空間,所以線程之間可以比進程之間更方便的共享數據和之間的互相通訊。
全局解釋器鎖(GIL)
Python代碼的執行由Python虛擬機(也叫解釋器主循環)來控制。Python在設計之初就考慮到在主循環中,同時只有一個線程在運行,就像在單CPU的系統中運行多個進程那樣,盡管在內存中可以存放多個程序,但是在任意時刻,只有一個程序在CPU中運行。
對于Python虛擬機的訪問由GIL來控制,正是這個鎖能保證同一時刻只有一個線程在運行。在多線程的環境中,Python虛擬機按一下方式運行:
- 設置GIL
- 切換到一個線程中運行
- 運行: a. 指定數量的字節碼的指令,或者線程主動讓出控制(可以調用time.sleep(0))
- 把線程設置為睡眠狀態
- 解鎖GIL
- 重復以上所用步驟
編寫擴展程序的程序員可以主動解鎖GIL,不過Python的開發人員則不用擔心在這個情況下你的Python代碼會被鎖住。
例如,對所有面向I/O的程序來說,GIL會在這個I/O調用之前被釋放,以允許其他的線程在這個線程等待I/O的時候允許。如何某線程并未使用很多I/O操作,它會在自己的時間片內一直占用CPU和GIL,也就是說,I/O密集型的Python程序比計算密集型的程序更能充分利用多線程環境的好處
沒有線程支持的情況
示例1
在單線程中順序執行兩個循環。一個循環結束后,另一個才能開始。總的時間是各個循環運行的時間之和。
#!/usr/bin/env python
from time import sleep, ctime
def loop0():
print 'start loop0 at: ', ctime()
sleep(4)
print 'loop0 done at: ', ctime()
def loop1():
print 'start loop1 at: ', ctime()
sleep(2)
print 'loop1 done at: ', ctime()
def main():
print 'starting at: ', ctime()
loop0()
loop1()
print 'all Done at: ', ctime()
if __name__ == '__main__':
main()
執行結果:loop0和loop1各自運行了4秒和2秒,整個程序耗時6秒,屬于串行執行
Python的threading模塊
Python提供了幾個用于多線程編程的模塊,包括thread
,threading
和Queue
等。
- thread模塊提供了基本的線程和鎖的支持
- threading提供了更高級、功能更強的線程管理的功能
- Queue模塊允許用戶創建一個可以用于多個線程之間共享數據的隊列數據結構。
示例2
#!/usr/bin/env python
import thread
from time import sleep, ctime
def loop0():
print 'start loop 0 at: ', ctime()
sleep(4)
print 'loop 0 done at: ', ctime()
def loop1():
print 'start loop1 at: ', ctime()
sleep(2)
print 'loop1 done at: ', ctime()
def main():
print 'starting at: ', ctime()
#start_new_thread()要求一定要有前兩個參數,就算運行的函數不需要傳參,也要傳一個空元組
thread.start_new_thread(loop0,())
thread.start_new_thread(loop1,())
#睡6秒來確保2個子線程都運行結束,防止主線程過早退出
sleep(6)
print 'all Done at: ', ctime()
if __name__ == '__main__':
main()
執行結果:loop0和loop1并行執行,2個函數執行耗時4秒
示例3
使用線程和鎖
引入鎖的概念是為了線程不用什么時候結束再做額外的等待,使用了鎖,我們就可以在兩個線程都退出后,馬上退出。(線程管理)
#!/usr/bin/env python
import thread
from time import sleep, ctime
loops = [4, 2]
def loop(nloop, nsec, lock):
print 'start loop', nloop, 'at: ',ctime()
sleep(nsec)
print 'loop',nloop,'done at: ',ctime()
lock.release()
def main():
print 'starting at: ', ctime()
locks = []
nloops = range(len(loops))
for i in nloops:
lock = thread.allocate_lock()
lock.acquire()
locks.append(lock)
for i in nloops:
thread.start_new_thread(loop, (i, loops[i], locks[i]))
for i in nloops:
while locks[i].locked():
pass
print 'all Done at: ', ctime()
if __name__ == '__main__':
main()
執行結果:
示例4
使用threading模塊的Thread類的一個join(),允許主線程等待子線程的結束
#!/usr/bin/env python
import threading
from time import sleep, ctime
loops = [4,2]
def loop(nloop, nsec):
print 'start loop', nloop, 'at: ',ctime()
sleep(nsec)
print 'loop',nloop,'done at: ',ctime()
def main():
print 'starting at: ', ctime()
threads = []
nloops = range(len(loops))
for i in nloops:
t = threading.Thread(target=loop, args=(i, loops[i]))
threads.append(t)
for i in nloops: #start thread
threads[i].start()
for i in nloops: #wait for all
threads[i].join() #threads to finish
print ' all Done at: ', ctime()
if __name__ == '__main__':
main()
執行結果: 所有的線程先都創建好之后,再一起調用start()函數啟動,而不是創建一個啟動一個,而且不需要在管理一堆鎖(分配鎖、獲得鎖、釋放鎖、檢查鎖等),只需簡單地對每個線程調用join()函數就可以了。
示例5
線程池threadpool
模塊
import time
from multiprocessing.dummy import Pool as ThreadPool
def printhello(str):
print "Hello ", str
time.sleep(2)
name_list =['world1', 'world2', 'world3', 'world4']
start_time = time.time()
pool = ThreadPool(10)
pool.map(printhello, name_list)
pool.close()
pool.join()
end_time = time.time()
print '%d second'% (end_time-start_time)
示例6
多線程爬取京東手機信息
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import time
import json
import requests
from lxml import etree
from pymongo import MongoClient
from multiprocessing.dummy import Pool as ThreadPool
reload(sys)
sys.path.append('..')
sys.setdefaultencoding('utf-8')
def request_html(url):
html = requests.get(url, headers=headers)
selector = etree.HTML(html.text)
goodslist = selector.xpath('//ul[@class="gl-warp clearfix"]/li')
for goods in goodslist:
try:
sku_id = goods.xpath('@data-sku')[0]
comment_url = 'https://item.jd.com/{}.html'.format(str(sku_id))
get_comment_data(comment_url)
except Exception as e:
print e
def get_comment_data(url):
dict = {}
html = requests.get(url, headers=headers)
selector = etree.HTML(html.text)
product_infos = selector.xpath('//ul[@class="parameter2 p-parameter-list"]')
for product in product_infos:
product_name = product.xpath('li[1]/@title')[0]
product_number = product.xpath('li[2]/@title')[0]
product_price = get_product_price(product_number)
gross_weight = product.xpath('li[4]/@title')[0]
commodity_origin = product.xpath('li[3]/@title')[0]
dict["商品名稱"] = product_name
dict["價格"] = product_price
dict["商品編號"] = product_number
dict["毛重"] = gross_weight
dict["產地"]=commodity_origin
save_to_mongodb(dict)
def get_product_price(sku): #直接xpath提取價格信息返回null,價格是js加載的
url = "https://p.3.cn/prices/mgets?&skuIds=J_%s" %str(sku)
html = requests.get(url, headers=headers).content
html_json = json.loads(html)
for info in html_json:
return info.get('p')
def save_to_mongodb(list):
client = mongoclient
db = client['goods']
posts = db.jd
posts.insert(list)
if __name__ == '__main__':
headers = {
'Cookie': 'ipLoc-djd=1-72-2799-0; unpl=V2_ZzNtbRZXF0dwChEEfxtbV2IKFQ4RUBcSdg1PVSgZCVAyCkBVclRCFXMUR1NnGFkUZgoZXkpcQxNFCHZXchBYAWcCGllyBBNNIEwHDCRSBUE3XHxcFVUWF3RaTwEoSVoAYwtBDkZUFBYhW0IAKElVVTUFR21yVEMldQl2VH4RWAVmBxVeS19AEHUJR1x6GFsBYQEibUVncyVyDkBQehFsBFcCIh8WC0QcdQ1GUTYZWQ1jAxNZRVRKHXYNRlV6EV0EYAcUX3JWcxY%3d; __jdv=122270672|baidu-pinzhuan|t_288551095_baidupinzhuan|cpc|0f3d30c8dba7459bb52f2eb5eba8ac7d_0_e1ec43fa536c486bb6e62480b1ddd8c9|1496536177759; mt_xid=V2_52007VwMXWllYU14YShBUBmIDE1NVWVNdG08bbFZiURQBWgxaRkhKEQgZYgNFV0FRVFtIVUlbV2FTRgJcWVNcSHkaXQVhHxNVQVlXSx5BEl0DbAMaYl9oUmofSB9eB2YGElBtWFdcGA%3D%3D; __jda=122270672.14951056289241009006573.1495105629.1496491774.1496535400.5; __jdb=122270672.26.14951056289241009006573|5.1496535400; __jdc=122270672; 3AB9D23F7A4B3C9B=EJMY3ATK7HCS7VQQNJETFIMV7BZ5NCCCCSWL3UZVSJBDWJP3REWXTFXZ7O2CDKMGP6JJK7E5G4XXBH7UA32GN7EVRY; __jdu=14951056289241009006573',
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36'
}
urls = ['https://search.jd.com/Search?keyword=%E6%89%8B%E6%9C%BA&enc=utf-8&qrst=1&rt=1&stop=1&vt=2&wq=%E6%89%8B%E6%9C%BA&cid2=653&cid3=655&page={}&click=0'.format(str(i)) for i in range(1, 200, 2)]
mongoclient = MongoClient('mongodb://172.16.110.163:27017/')
pool = ThreadPool(4)
start = time.time()
pool.map(request_html, urls)
pool.close()
pool.join()
end = time.time()
print 'Total Time:' + str(end - start) + 's'