目录
简单使用
传统单线程的调用方式:
import time
#传统单线程的调用方式
def draw1():
for i in range (3):
print("正在画画1")
time.sleep(1)
def draw2():
for i in range(3):
print("正在画画2")
time.sleep(1)
def start():
draw1()
draw2()
if __name__=="__main__":
start()
执行结果是每隔1s就会输出一行记录,总共花费六秒执行完成。
使用多线程。
import time
import threading
#传统单线程的调用方式
def draw1():
for i in range (3):
print("正在画画1")
time.sleep(1)
def draw2():
for i in range(3):
print("正在画画2")
time.sleep(1)
def start():
#使用threading.Thread创建两个线程,target指定要运行的方法,注意不要带括号,不然就就是得到了返回值
thread1 = threading.Thread(target=draw1)
thread2 = threading.Thread(target=draw2)
#启动线程,只有线程调用start之后才真正要执行
thread1.start()
thread2.start()
if __name__=="__main__":
start()
运行结果:
相关用法
1:
#查看当前有多少线程数
threading.enumerate()
输出结果:
包括了线程了名称
[<_MainThread(MainThread, started 24996)>, <Thread(Thread-1, started 12880)>, <Thread(Thread-2, started 24088)>]
2:
查看当前线程对象,获取当前线程名称,合并其它线程等。
threading.current_thread()
3:
threading.Thread类的使用。可以自定义一个类来继承这个类,实现里面的一个run方法,这样在启动线程的时候会自动运行run方法里面的代码。
import threading
import time
class ThreadClass(threading.Thread):
def run(self):
for x in range(3):
print('%s正在写代码' % threading.current_thread())
time.sleep(1)
class ThreadClass2(threading.Thread):
def run(self):
for x in range(3):
print('%s正在写代码' % threading.current_thread())
time.sleep(1)
def starThrea():
t1 = ThreadClass2()
t2 = ThreadClass()
t1.start()
t2.start()
if __name__=='__main__':
starThrea()
运行结果:
多线程共享全局变量以及锁机制
python当然也有多线程的问题,举个简单的例子,两个线程对同一个变量进行修改:如下
import threading
import time
VALUE=0
class ThreadClass(threading.Thread):
def run(self):
global VALUE
for x in range(100000):
VALUE+=1
print("value: %d" % VALUE)
def starThrea():
t1 = ThreadClass()
t2 = ThreadClass()
t1.start()
t2.start()
if __name__=='__main__':
starThrea()
由于多线程的问题运行的结果,我们是不可控的。
#第一次运行结果
value: 100000
value: 176352
#第二次运行结果
value: 100000
value: 168564
#第三次运行结果
value: 100000
value: 188498
在python对于多线程中都会修改的变量要进行加锁处理,python加锁也很简单,如下:
import threading
import time
VALUE = 0
#得到锁对象
gLock = threading.Lock()
class ThreadClass(threading.Thread):
def run(self):
global VALUE
#获取锁
gLock.acquire()
for x in range(100000):
VALUE+=1
#释放锁
gLock.release()
print("value: %d" % VALUE)
def starThrea():
t1 = ThreadClass()
t2 = ThreadClass()
t1.start()
t2.start()
if __name__=='__main__':
starThrea()
注意锁要加在全局变量修改的地方,锁的范围越小越好。
Condition版生产者消费者模式锁
Lock虽然可以加锁,但是每次使用的时候都要重新上锁,其它线程得不到锁就只能等待,加锁本来就是个很耗性能的操作,所以有了一种更高效的加锁方式。threading.Condition类。可以在没有数据的时候释放锁并处于阻塞状态,这样其它线程可以得到锁继续执行,当条件满足后其它线程 可以唤醒阻塞的线程继续执行。
关于Condition主要函数介绍:
1:acquire :加锁
2: release: 释放锁
3:wait :将当前线程处于等待状态,并且会释放锁。可以被其它线程使用notify和notify_all函数唤醒。被唤醒之后会继续等待上锁,上锁后继续往下执行。
4:notify: 唤醒某个等待的线程,默认是等待最久的那个线程唤醒。
5:notify_all: 唤醒所有正在等待的线程。
注意: nofity和notify_all不会释放锁,并且需要在释放锁之前调用即在调用release之前调用。
Queue线程安全队列。
在多线程访问全局变量时,加锁是一个经常的过程。如果你是想把一些数据存储到某个队列中,Python内置了一个queue模块。这个模块中提供了同步的,线程安全的队列类,包括FIFO(先进先出)队列Queue.LIFO(后进先出)队列LifoQueue。
这些队列都实现了锁原语(可以理解为原子操作,即要么不做,要么都做完),可以在多线程中直接使用。用队列实现线程间的同步。相关函数如下:
1:初始化Queue(maxsize)创建一个先进先出的队列
2:qsize()返回队列的大小
3:empty()判断队列是否为空
4:full()判断队列是否满了
5:get()从队列中取最后一个数据,有一个blcok参数,布尔类型,表示获取值的时候是否阻塞,默认为True。即如果队列中没有值该方法会阻塞。
6:put() 将一个数据放入队列中,方法也有一个block,布尔类型,表示放入值的时候是否阻塞,默认为True,即如果队列已经满了,方法会阻塞。
爬取斗图网实例
import threading
from lxml import etree
import requests
from urllib import request
import os
import time
import uuid
from queue import Queue
HEADER={
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36"
}
#生产者
class Product(threading.Thread):
#需要把定义的页数队列在定义线程的时候当作参数传递过来传递过来,因此需要重写init方法
# *args,**kwargs这两个参数形式可以代表可以接收任意参数
def __init__(self,img_page_queue,img_url_queue,*args,**kwargs):
super(Product,self).__init__(*args,**kwargs)
self.pge_queue=img_page_queue
self.img_rul=img_url_queue
def run(self) :
while True:
if self.pge_queue.empty():
break
url = self.pge_queue.get()
print(url)
try:
self.url_parse(url)
except Exception as e:
print("error1:",e)
def url_parse(self,url):
proxy = {"http": "121.232.148.167:9000"}
response = requests.get(url=url, headers=HEADER, proxies=proxy)
responsetext = response.text
# print(responsetext)
imghtml = etree.HTML(responsetext)
imgelement = imghtml.xpath("//div[@class='col-xs-6 col-sm-3']//img[@class='lazy image_dtb img-responsive']")
for i in imgelement:
#使用uuid来设置文件名
filename= uuid.uuid4()
# file_index = random.randint(1, 10000000)
# print(etree.tostring(i,encoding="utf-8").decode("utf-8"))
# 图片的地址
img_url = i.get("data-original")
try:
# 获取图片的后缀名
suffix = os.path.splitext(img_url)[1]
except Exception as e:
print("error2:", e,img_url)
#把得到的图片url以及文件名放入队列,注意放入的是个元组
self.img_rul.put((img_url,"img/"+str(filename)+suffix))
class Consumer(threading.Thread):
# 需要把定义的页数队列在定义线程的时候当作参数传递过来传递过来,因此需要重写init方法
# *args,**kwargs这两个参数形式可以代表可以接收任意参数
def __init__(self, img_page_queue, img_url_queue, *args, **kwargs):
super(Consumer, self).__init__(*args, **kwargs)
self.pge_queue = img_page_queue
self.img_rul = img_url_queue
def run(self):
while True:
if self.img_rul.empty() and self.pge_queue.empty():
break
#接收也要用两个参数
img_url, filename=self.img_rul.get()
try:
#开始下载
request.urlretrieve(img_url, filename)
except Exception as e:
print("error3:", e,img_url)
print(img_url,filename,"下载完成")
def main():
img_page_queue = Queue(50)
img_url_queue = Queue(100000)
# 生成50页的url地址
for i in range(1, 50):
url = "https://www.doutula.com/article/list/?page=" + str(i)
img_page_queue.put(url)
for x in range(5):
t=Product(img_page_queue,img_url_queue)
t.start()
time.sleep(4)
for i in range(5):
t=Consumer(img_page_queue,img_url_queue)
t.start()
if __name__=='__main__':
main()