主题
正常情况下,程序的运行按顺序执行,但是涉及某些操作,等待结果完成却是非常耗时的操作,比如爬虫进行IO操作等,当涉及的量较大的时候,同步执行的程序十分的耗时,为了使得支持并发操作,缩减程序运行的时间,提高效率。多线程技术就是为了实现这样的功能。
多线程编程中涉及到许多概念:
并发式编程
多线程和多进程
线程安全
线程的声明周期
线程的类型
创建线程
线程间的通信
事件通知
锁和可重入锁
线程挂起
实战型演示多线程编程,并列出我认为写的好的参考文献,供大家参考。
0.
全文思路
1.
threading
函数
类包装线程对象:实现run方法
Queue:实现数据间的通信,生产者和消费者模式
# 1 创建新线程:<span class="wp_keywordlink_affiliate"><a href="https://www.168seo.cn/tag/threading-thread" title="View all posts in threading.Thread" target="_blank">threading.Thread</a></span>(func, args=(,)) 启动线程活动:start() 等待至线程终止:join() # 2 生产者-消费者模型 Queue.put() Queue.get() # 3 互斥锁同步:数据共享;当多个线程都修改某一个共享数据的时候,需要进行同步控制。 线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性 # 创建锁 mutex = <span class="wp_keywordlink_affiliate"><a href="https://www.168seo.cn/tag/threading" title="View all posts in threading" target="_blank">threading</a></span>.Lock() # 锁定 mutex.acquire([timeout]) # 释放 mutex.release() # 4 死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。 # 5 可重入锁:一个线程“迭代”请求同一个资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
# 1
创建新线程:
threading
.
Thread
(
func
,
args
=
(
,
)
)
启动线程活动:
start
(
)
等待至线程终止:
join
(
)
# 2
生产者
-消费者模型
Queue
.
put
(
)
Queue
.
get
(
)
# 3
互斥锁同步:数据共享
;当多个线程都修改某一个共享数据的时候,需要进行同步控制。
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定
/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性
# 创建锁
mutex
=
threading
.
Lock
(
)
# 锁定
mutex
.
acquire
(
[
timeout
]
)
# 释放
mutex
.
release
(
)
# 4
死锁:在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。
# 5
可重入锁:一个线程“迭代”请求同一个资源
|
2.目标网站:
目标:获取一年内的停复牌信息
目标网址:http://www.cninfo.com.cn/cninfo-new/memo-2?queryDate=2016-11-17&queryType=queryType1
网址和日期相关
周末无数据
获取11月份有效url的方法:
不知道为啥我写的这么复杂又是回调函数,又是各种函数调用。
def crawlurl(year=2016, month=11, day=None, <span class="wp_keywordlink_affiliate"><a href="https://www.168seo.cn/tag/queue" title="View all posts in queue" target="_blank">queue</a></span>=None, *, callback, lastcall): days = [i for i in range(1, 31)] for day in days: if callback(year, month, day=day): data = datetime(year, month, day) URL = lastcall(data) <span class="wp_keywordlink_affiliate"><a href="https://www.168seo.cn/tag/queue" title="View all posts in queue" target="_blank">queue</a></span>.put(("url", URL)) # 判断日期是否有效,周末和未来网站无数据 def is_valid_day(year=2016, month=11, day=None): data = datetime(year=year, month=month, day=day) if data.weekday() in range(0, 5) and data <= datetime.today(): return True # 构造url方法 def geturl(data): url = "http://www.cninfo.com.cn/cninfo-new/memo-2?queryDate=%s&queryType=queryType1" return url % changedatetime(data) # 将日期格式化成2016-11-17形式 def changedatetime(date): return date.strftime('%Y-%m-%d') A = Queue() allurl = crawlurl(2016,11, queue= A, callback=is_valid_day, lastcall=geturl) print(A.qsize()) # 截止17号,网站存在13个有效日期:去除5,6,12,13周末日 A.put(("None", -1)) while True: _, url = A.get() if url == -1: break print(url)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
def
crawlurl
(
year
=
2016
,
month
=
11
,
day
=
None
,
queue
=
None
,
*
,
callback
,
lastcall
)
:
days
=
[
i
for
i
in
range
(
1
,
31
)
]
for
day
in
days
:
if
callback
(
year
,
month
,
day
=
day
)
:
data
=
datetime
(
year
,
month
,
day
)
URL
=
lastcall
(
data
)
queue
.
put
(
(
"url"
,
URL
)
)
# 判断日期是否有效,周末和未来网站无数据
def
is_valid_day
(
year
=
2016
,
month
=
11
,
day
=
None
)
:
data
=
datetime
(
year
=
year
,
month
=
month
,
day
=
day
)
if
data
.
weekday
(
)
in
range
(
0
,
5
)
and
data
<=
datetime
.
today
(
)
:
return
True
# 构造url方法
def
geturl
(
data
)
:
url
=
"http://www.cninfo.com.cn/cninfo-new/memo-2?queryDate=%s&queryType=queryType1"
return
url
%
changedatetime
(
data
)
# 将日期格式化成2016-11-17形式
def
changedatetime
(
date
)
:
return
date
.
strftime
(
'%Y-%m-%d'
)
A
=
Queue
(
)
allurl
=
crawlurl
(
2016
,
11
,
queue
=
A
,
callback
=
is_valid_day
,
lastcall
=
geturl
)
print
(
A
.
qsize
(
)
)
# 截止17号,网站存在13个有效日期:去除5,6,12,13周末日
A
.
put
(
(
"None"
,
-
1
)
)
while
True
:
_
,
url
=
A
.
get
(
)
if
url
==
-
1
:
break
print
(
url
)
|
一、实例化Thread类来使用多线程
Thread(target=func, args=())
# url是上文获取的url, queue 用来将IO操作抓取的网页信息存入队列中 def download(url, queue): headers = {"Host": "www.cninfo.com.cn", "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36"} try: resp = requests.get(url, headers=headers) if resp.status_code == 200: queue.put(("response", resp.text)) except: print("Connection wrong")
1
2
3
4
5
6
7
8
9
10
|
# url是上文获取的url, queue 用来将IO操作抓取的网页信息存入队列中
def
download
(
url
,
queue
)
:
headers
=
{
"Host"
:
"www.cninfo.com.cn"
,
"User-Agent"
:
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36"
}
try
:
resp
=
requests
.
get
(
url
,
headers
=
headers
)
if
resp
.
status_code
==
200
:
queue
.
put
(
(
"response"
,
resp
.
text
)
)
except
:
print
(
"Connection wrong"
)
|
# 这里整体的思路是: # 1. 将获取的url存放在url_queue队列中 # 2. 将url_queue队列中的url 传入下载函数中 # 3. 将获取的网页信息数据存入:response_queue队列中 url_queue = Queue() response_queue = Queue() i = crawlurl(2016,11, queue= url_queue, callback=is_valid_day, lastcall=geturl) print(url_queue.qsize()) # 13 url_queue.put(("None", -1)) # 插入标志信息好让函数返回 <span class="wp_keywordlink_affiliate"><a href="https://www.168seo.cn/tag/thread" title="View all posts in thread" target="_blank">thread</a></span>s = [] while True: _, url = url_queue.get() if url == -1: break t = Thread(target=download, args=(url, response_queue)) <span class="wp_keywordlink_affiliate"><a href="https://www.168seo.cn/tag/thread" title="View all posts in thread" target="_blank">thread</a></span>s.append(t) t.start() # 启动线程 for j in threads: j.join() # 线程挂起,主线程会等待子线程全部完成才继续运行 print(response_queue.qsize()) # 13 response_queue.put(("None", -1)) while True: _, response = response_queue.get() if response == -1: break print(len(response)) # 查看是否真的获取网页信息 #33087 #36111 #39310 #43046 #36163 #40661 #42014 #48080 #52088 #34507 #40294 #28014 #44018
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
# 这里整体的思路是:
# 1. 将获取的url存放在url_queue队列中
# 2. 将url_queue队列中的url 传入下载函数中
# 3. 将获取的网页信息数据存入:response_queue队列中
url_queue
=
Queue
(
)
response_queue
=
Queue
(
)
i
=
crawlurl
(
2016
,
11
,
queue
=
url_queue
,
callback
=
is_valid_day
,
lastcall
=
geturl
)
print
(
url_queue
.
qsize
(
)
)
# 13
url_queue
.
put
(
(
"None"
,
-
1
)
)
# 插入标志信息好让函数返回
threads
=
[
]
while
True
:
_
,
url
=
url_queue
.
get
(
)
if
url
==
-
1
:
break
t
=
Thread
(
target
=
download
,
args
=
(
url
,
response_queue
)
)
threads
.
append
(
t
)
t
.
start
(
)
# 启动线程
for
j
in
threads
:
j
.
join
(
)
# 线程挂起,主线程会等待子线程全部完成才继续运行
print
(
response_queue
.
qsize
(
)
)
# 13
response_queue
.
put
(
(
"None"
,
-
1
)
)
while
True
:
_
,
response
=
response_queue
.
get
(
)
if
response
==
-
1
:
break
print
(
len
(
response
)
)
# 查看是否真的获取网页信息
#33087
#36111
#39310
#43046
#36163
#40661
#42014
#48080
#52088
#34507
#40294
#28014
#44018
|
二、继承Thread类,重写run方法
class Download(Thread): def __init__(self, url, queue): super().__init__() self.url = url self.queue = queue def run(self): response = self.download() self.queue.put(("response", response)) def download(self): headers = {"Host": "www.cninfo.com.cn", "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36"} try: resp = requests.get(self.url, headers=headers) if resp.status_code == 200: return resp.text except: print("Connection wrong") # 获取url url_queue = Queue() response_queue = Queue() i = crawlurl(2016,11, queue= url_queue, callback=is_valid_day, lastcall=geturl) print(url_queue.qsize()) url_queue.put(("None", -1)) threads2 = [] response_queue2 = Queue() while True: _, url = url_queue.get() if url == -1: break t2 = Download(url, response_queue2) threads2.append(t2) for i in threads2: i.start() for j in threads2: j.join() print(response_queue2.qsize()) # 13 response_queue2.put(("None", -1)) while True: _, response = response_queue2.get() if response == -1: break print(len(response)) #33087 #34507 #28014 #44018 #48080 #36111 #43046 #36163 #40294 #40661 #39310 #52088 #42014
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
class
Download
(
Thread
)
:
def
__init__
(
self
,
url
,
queue
)
:
super
(
)
.
__init__
(
)
self
.
url
=
url
self
.
queue
=
queue
def
run
(
self
)
:
response
=
self
.
download
(
)
self
.
queue
.
put
(
(
"response"
,
response
)
)
def
download
(
self
)
:
headers
=
{
"Host"
:
"www.cninfo.com.cn"
,
"User-Agent"
:
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.87 Safari/537.36"
}
try
:
resp
=
requests
.
get
(
self
.
url
,
headers
=
headers
)
if
resp
.
status_code
==
200
:
return
resp
.
text
except
:
print
(
"Connection wrong"
)
# 获取url
url_queue
=
Queue
(
)
response_queue
=
Queue
(
)
i
=
crawlurl
(
2016
,
11
,
queue
=
url_queue
,
callback
=
is_valid_day
,
lastcall
=
geturl
)
print
(
url_queue
.
qsize
(
)
)
url_queue
.
put
(
(
"None"
,
-
1
)
)
threads2
=
[
]
response_queue2
=
Queue
(
)
while
True
:
_
,
url
=
url_queue
.
get
(
)
if
url
==
-
1
:
break
t2
=
Download
(
url
,
response_queue2
)
threads2
.
append
(
t2
)
for
i
in
threads2
:
i
.
start
(
)
for
j
in
threads2
:
j
.
join
(
)
print
(
response_queue2
.
qsize
(
)
)
# 13
response_queue2
.
put
(
(
"None"
,
-
1
)
)
while
True
:
_
,
response
=
response_queue2
.
get
(
)
if
response
==
-
1
:
break
print
(
len
(
response
)
)
#33087
#34507
#28014
#44018
#48080
#36111
#43046
#36163
#40294
#40661
#39310
#52088
#42014
|