Fork me on GitHub

python自动化编程-第九天

python自动化编程-第九天


一、paramiko 模块

Paramiko 是 SSHv2 协议的 Python (2.7、3.4 +) 实现, 同时提供客户端和服务器功能。

1.1 SSH的基本原理

SSH是 Secure Shell的缩写,是传统的网络服务程序,SSH有很多功能,它既可以代替Telnet,又可以为FTP、PoP、甚至为PPP提供一个安全的"通道"。从客户端来看,SSH提供两种级别的安全验证。

第一种基于口令的安全验证:
只要你知道自己帐号和口令,就可以登录到远程主机。所有传输的数据都会被加密,但是不能保证你正在连接的服务器就是你想连接的服务器。可能会有别的服务器在冒充真正的服务器,也就是受到“中间人”这种方式的攻击

第二种是基于密钥的安全验证:
需要依靠密匙,也就是你必须为自己创建一对密匙,并把公用密匙放在需要访问的服务器上。如果你要连接到SSH服务器上,客户端软件就会向服务器发出请求,请求用你的密匙进行安全验证。服务器收到请求之后,先在该服务器上你的主目录下寻找你的公用密匙,然后把它和你发送过来的公用密匙进行比较。如果两个密匙一致,服务器就用公用密匙加密“质询”(challenge)并把它发送给客户端软件。客户端软件收到“质询”之后就可以用你的私人密匙解密再把它发送给服务器。
用这种方式,你必须知道自己密匙的口令。但是,与第一种级别相比,第二种级别不需要在网络上传送口令。

1.2 SSH的使用

SSH在linux中有服务器端和客户端,进程分别是:

sshd:ssh daemon
ssh:ssh client、scp、sftp

sshd:配置文件 /etc/ssh/sshd_config
ssh: 配置文件 /etc/ssh/ssh_config

只有在认可的服务器列表中的主机(~/.ssh/known_hosts)才允许远程登录。

客户端程序:
	ssh  [options]  [user@]host  [COMMAND]
	ssh  [options]  [-l  user]  host  [COMMAND]

省略用户名:
	使用本地主机当前使用的用户名作为远程登录的用户名;

1.2.1 基于密钥认证

首先生成一对密钥对,一般使用非对称密钥RSA。

ssh-keygen [-q]  [-b bits]  [-t type]  [-f output_keyfile] [-N passphrase] 
					-t  {rsa|ecdsa|dsa}:公钥加密算法类型;
						rsa:默认算法,密钥默认长度为2048bits;
						dsa:密钥长度固定为1024bits;
						ecdsa:密钥长度为256/384/512bits;
					-b bits:指明密钥长度;
					-N passphrase:私钥加密密码;
					-f output_keyfile:生成密钥的保存位置;

然后在本地主机上,将公钥复制到要登录的远程主机的某用户的家目录下的特定文件中(~/.ssh/authorized_keys),需要注意的是基于密钥的认证是单向的

ssh-copy-id  [-i [identity_file]]  [-p port]  [-o ssh_option]  [user@]hostname
					-i [identity_file]:公钥文件

1.2.2 scp传输文件

Scp 传输文件,
PULL:  scp  [options]  [user@]host:/PATH/TO/SOMEFILE   /PATH/TO/SOMEFILE
PUSH:    scp  [options]  /PATH/TO/SOMEFILE   [user@]host:/PATH/TO/SOMEFILE

	常用选项:
		-r:递归复制;
		-p:保持原文件的权限信息;
		-q:静默模式;
		-P PORT:指明远程主机ssh协议监听的端口;

1.3 使用paramiko 实现SSH客户端

import paramiko

# 创建ssh对象
ssh = paramiko.SSHClient()

# 允许连接不再know_hosts文件中的主机
# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())


# 连接服务器
ssh.connect(hostname='192.168,255,128', port=22, username='czlan', password='123456')

# 执行命令

stdin, stdout, stderr = ssh.exec_command('df')

# 获取命令结果
result = stdout.read()

# 关闭连接
ssh.close()

1.4 基于密钥认证的SSH客户端


import paramiko

private_key = paramiko.RSAKey.from_private_key_file('/home/auto/.ssh/id_rsa')


# 创建ssh对象
ssh = paramiko.SSHClient()

# 允许连接不再know_hosts文件中的主机
# ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())


# 连接服务器
ssh.connect(hostname='192.168,255,128', port=22, username='czlan', pkey=private_key)

# 执行命令

stdin, stdout, stderr = ssh.exec_command('df')

# 获取命令结果
result = stdout.read()

print(result.decode())

# 关闭连接
ssh.close()

1.5 使用paramiko实现ssh scp功能

import paramiko

transport = paramiko.Transport(('hostname',22))

transport.connect(username='czlan',password='123')

sftp = paramiko.SFTPClient.from_transport(transport)

#将location.py上传至服务器 /tmp/作用域.py
sftp.put('/tmp/location.py','/tmp/作用域.py')

# 将remove_ssh 下载到本地local_path
sftp.get('remove_path','local_path')

transport.close()

1.6 基于密钥的ssh 的scp功能

import paramiko

private_key = paramiko.RSAKey.from_private_key_file('/home/auto/.ssh/id_rsa')

transport = paramiko.Transport(('hostname',22))

transport.connect(username='czlan',pkey=private_key)

sftp = paramiko.SFTPClient.from_transport(transport)

#将location.py上传至服务器 /tmp/作用域.py
sftp.put('/tmp/location.py','/tmp/作用域.py')

# 将remove_ssh 下载到本地local_path
sftp.get('remove_path','local_path')

transport.close()

二、进程与线程

2.1 什么是进程

程序的执行实例称为进程。
程序是指令的集合,它是进程运行的静态描述文本,进程是程序的一次执行活动,属于动态概念;

每个进程都提供执行程序所需的资源。进程具有虚拟地址空间、可执行代码、对系统对象的打开句柄、安全上下文、惟一进程标识符、环境变量、优先级、最小和最大工作集大小,以及至少一个执行线程。每个进程都是从一个线程开始的,通常称为主线程,但是可以从它的任何线程创建额外的线程;子线程也可以创建子线程。

2.2 什么是线程

线程就是操作系统能够进行运算调度CPU的最小单位;操作系统与cpu之间交互是通过发送一系列的指令来完成的,这些指令的集合就是线程;也可以理解成线程就是一堆指令,被包含在进程中。
一条进程指的是进程中一个单一顺序的控制流,一个进程可以并发多个线程,每条线程并行执行不同的任务。

也可以说一个线程是一个执行上下文,它是一个CPU需要执行一系列指令的所有信息。

假设你正在读一本书,你现在想休息一下,但是你希望当你回来时,能够从你停止的地方继续阅读。实现这一目标的一种方法是记下页码、行号和字号。所以你读一本书的执行上下文是这三个数字。
那如果你有一个室友,而且她使用的也是这种方法,她可以在你不用的时候拿着书,然后从她停止的地方继续阅读。然后当他休息时,你再把它拿回来,从你所在的地方重新开始。

这种就是执行上下文。

线程的工作方式与其相同。一个CPU给你的错觉是它同时在做多个计算。它通过在每个计算上花费一点时间。它可以这样做,是因为它对每个计算都有一个执行上下文。就像你可以和你的朋友分享一本书一样,而许多任务可以共享一个CPU。

在技术层面上,执行上下文(因此是一个线程)由CPU寄存器的值组成。

最后线程与进程的不同:线程是执行的上下文,而进程是与计算相关的一堆资源。一个进程可以有一个或多个线程。

说明:与进程相关的资源包括内存页(进程中的所有线程都具有相同的内存视图)、文件描述符(例如,打开的套接字)和安全凭据(例如启动进程的用户的ID)。

2.3 进程与线程的区别

1. 线程共享创建它的进程的内存地址空间;每个进程有自己独立的内存地址空间。
2. 线程可以直接访问其进程的数据段;进程有自己的父进程数据段的副本;
3. 同一个进程的线程可以直接与进程的其他线程通信;进程必须使用进程间通信来与兄弟进程通信。
4. 新线程很容易创建;新进程的创建则需要拷贝父进程。
5. 线程可以对相同进程的线程进行相当大的控制;进程只能对子进程进行控制。
一个线程可以控制和操作同一个进程里的其他线程,但是进程只能操作子进程。

6. 对主线程的更改(取消、优先级更改等)可能会影响进程的其他线程的行为;对父进程的更改则不会影响子进程。

三、多线程

3.1 基本语法

3.1.1 常用创建线程的方法


import threading  #导入线程模块

def run(n):       #设置线程处理函数
    print('task', n)

t = threading.Thread(target=run, args=(‘1’,)) # 配置线程,调用动作(函数),若有参数,则必须要加‘,’
t.start()  # 启动线程

3.1.2 通过类来创建线程

import threading, time


class MyThread(threading.Thread):
    def __init__(self, n,sleep_time):
        super(MyThread, self).__init__()
        self.n = n
        self.sleep_time = sleep_time


    def run(self):  # 方法名是固定的,不用类来写的话,无所谓
        print('runnint task', self.n)
        time.sleep(self.sleep_time)
        print('task done,',self.n)

t1 = MyThread('t1',2)
t2 = MyThread('t2',4)

t1.start()
t2.start()

3.2 join

我们在运行程序时,往往需要主线程等待子线程的运行结果,才能继续运行主线程;此时,就需要主线程来等待子线程运行完毕以后,才能继续运行;在python中,threading模块定义了一个方法,join方式,就是专门用来实现此功能的;


import threading, time


def run(n):
    print('task', n)
    time.sleep(2)
    print('task done', n, threading.current_thread())


start_time = time.time()

t_objs = [] #线程存储列表

for i in range(50):
    t = threading.Thread(target=run, args=('t%s' % i,))
    t.start()
    t_objs.append(t)  # 为了不阻塞后面线程的启动,不再这里join,先放到一个列表里;

for t in t_objs:  # 循环线程列表,等待所有线程执行完毕
    t.join()

print('-----all threads has finished.....', threading.current_thread(), threading.active_count())
print('cost:', time.time() - start_time)

上面这个代码的意思就是 循环生成50个线程,等待这50个线程全部执行完毕以后,在运行主线程的print操作;

3.3 守护线程

守护线程,顾名思义就是守护主线程的线程,若程序的主线程执行完毕结束,则守护线程也就关闭了;
程序启动时只有一个线程,就是主线程;但是当启动多线程时,一般情况下,主线程会执行,程序不会立即结束,而是会等到其他线程都执行完毕后再结束;
守护线程就是如果主线程执行完毕,此时程序就不会等待守护线程执行完毕,而直接结束进程;

总结:
主线程不等其他线程,但是程序执行完毕要等所有的线程执行完毕;而主线程是不会等待守护线程执行完毕的,守护线程在主线程执行完毕后,就会强制中断;

import threading, time


def run(n):
    print('task', n)
    time.sleep(2)
    print('task done', n, threading.current_thread())


start_time = time.time()

t_objs = []

for i in range(50):
    t = threading.Thread(target=run, args=('t%s' % i,))
    t.setDaemon(True)  # 把当前线程设置为守护线程;一定要在start之前设置;
    t.start()
    t_objs.append(t)  # 为了不阻塞后面线程的启动,不再这里join,先放到一个列表里;

for t in t_objs:  # 循环线程列表,等待所有线程执行完毕
    t.join()

print('-----all threads has finished.....', threading.current_thread(), threading.active_count())
print('cost:', time.time() - start_time)

3.4 其他一些方法

threading.current_thread()  # 显示当前线程
threading.active_count()    # 显示当前激活的线程数量

3.5 GIL锁

GIL锁,简单的说就是同一时间只有一个线程能够工作。
这时个Cpython的缺陷;只有Cpython才有的缺陷,不可能去掉GIL。Cpython的多线程实际上只有一个线程在工作;

3.6 互斥锁(mutex)

虽然有GIL,但是多个线程在CPU中运行时有可能会被强制释放GIL;这时如果多个线程在修改同一份数据时就可能会出错;因此需要互斥锁;保证同一时间只能有一个线程来修改数据。

注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁

import threading, time


def run(n):
    lock.acquire()  #增加锁,增加线程锁,只有在2.x中才要加,3.0中是不需要的,此时就变成串行的,这个锁在3.0中还是要加
    global num
    time.sleep(1)   #就能看出是不是串行的
    num += 1
    lock.release()   #释放锁

lock = threading.Lock() #定义锁

num = 0
t_objs = []  # 线程实例列表

for i in range(100):
    t = threading.Thread(target=run, args=('t%s' % i,))
    t.start()
    t_objs.append(t)  # 为了不阻塞后面线程的启动,不再这里join,先放到一个列表里;

for t in t_objs:  # 循环线程列表,等待所有线程执行完毕
    t.join()

print('-----all threads has finished.....', threading.current_thread(), threading.active_count())
print('num:', num)

上面的代码,有100个线程同时修改num变量,如果不加互斥锁,则这100个线程都可以修改num变量,那么结果可能就不是100;为了防止结果出错,则需要加上互斥锁;
具体过程如下图:

配置互斥锁的步骤:
1、首先定义一个锁,
2、然后在线程的方法中,要修改数据之前增加锁
3、数据修改完成后,要释放锁

3.7 递归锁

如果同一时间有两把互斥锁存在时,在python中则会报错,此时则需要使用递归锁。


import threading, time

def run1():
    print("grab the first part data")
    lock.acquire()
    global num
    num += 1
    lock.release()
    return num

def run2():
    print("grab the second part data")
    lock.acquire()
    global num2
    num2 += 1
    lock.release()
    return num2

def run3():
    lock.acquire()
    res = run1()
    print('--------between run1 and run2-----')
    res2 = run2()
    lock.release()
    print(res, res2)

if __name__ == '__main__':

    num, num2 = 0, 0
    lock = threading.RLock()  #递归锁
    for i in range(10):
        t = threading.Thread(target=run3)
        t.start()

while threading.active_count() != 1:
    print(threading.active_count())
else:
    print('----all threads done---')
    print(num, num2)

3.8 Semaphore(信号量)

互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据。

import threading, time


def run(n):
    semaphore.acquire()
    time.sleep(1)
    print("run the thread: %s\n" % n)
    semaphore.release()


if __name__ == '__main__':

    num = 0
    semaphore = threading.BoundedSemaphore(5)  # 最多允许5个线程同时运行
    for i in range(20):
        t = threading.Thread(target=run, args=(i,))
        t.start()

while threading.active_count() != 1:
    pass  # print threading.active_count()
else:
    print('----all threads done---')
    print(num)

需要注意的是,信号量中,可以设置同时存在的线程数量;每个线程运行结束后,都会有一个新线程被放入信号量中,但是同时只能存在N(N是数字,自己设置)个线程,

3.9 events

事件是一个简单的同步对象;
事件表示一个内部标志和线程。
可以等待设置标志、设置标志或清除标志本身。

event = threading.Event()

客户机线程可以等待设置标志。
event.wait()

一个服务器线程可以设置或重置它。
event.set()
event.clear()
如果设置了标志,等待方法不会执行任何操作。
如果标记被清除,等待将阻塞直到它再次被设置。
任何数量的线程都可以等待相同的事件。

import time
import threading

event = threading.Event()

def lighter():
    count = 0
    event.set() #先设置为绿灯
    while True:
        if count > 5 and count < 10: #改成红灯
            event.clear()  #把标志位请了
            print('\033[41;1mred light is on...\033[0m')
        elif count > 10:
            event.set() #变绿灯
            count = 0
        else:
            print('\033[42;1mgreen light is on...\033[0m')

        time.sleep(1)
        count +=1

def car(name):
    while True:
        if event.is_set(): #代表绿灯
            print('[%s] running....'% name)
            time.sleep(1)
        else:
            print('[%s] sees red light,waiting......'% name)
            event.wait()
            print('\033[34;1m[%s] green light is on,start going....\033[0m'%name)


light = threading.Thread(target=lighter,)
light.start()
car1 = threading.Thread(target=car,args=('Tesla',))
car1.start()

上述代码的意思是,首先设置一个标志位表示绿灯,然后car看到绿灯时,则继续行驶,没过一段时间将设置为清空,表示红灯,此时car将停止,当红灯4秒中后,则继续设置标志位,表示绿灯;以此循环执行,表示一个红绿灯程序;
event.wait()就是当标志位设置后,则继续执行下面的代码

3.10 队列

当必须在多个线程之间安全地交换信息时,队列在线程编程中特别有用。

3.10.1 队列的主要作用:

提高双方效率,运行效率
程序的解耦,将紧密的联系变成松散的联系

队列就是一个容器,但是这个容器是有序的;

列表与队列的区别,取出数据时,数据还在列表中,除非手动删除,但是队列,就没有来了;
队列的数据放在内存中

3.10.2 主要方法:

class queue.Queue(maxsize=0) #先入先出
class queue.LifoQueue(maxsize=0) #last in fisrt out 
class queue.PriorityQueue(maxsize=0) #存储数据时可设置优先级的队列

优先队列的构造函数。maxsize是一个整数,它设置可以在队列中放置的条目数的上界限制。一旦达到这个大小,插入将阻塞,直到使用队列项为止。如果maxsize小于或等于0,队列大小是无限的。

最低值的条目首先被检索(最低的值条目是排序后返回的条目)[0]。条目的典型模式是表单中的元组:(priority_number, data)。

exception queue.Empty
当非阻塞get(或get_nowait())被调用在一个空的队列对象上时,异常会发生。

exception queue.Full
非阻塞put()(或put_nowait())被调用在一个已满的队列对象时引发异常。

Queue.qsize()
Qurere.empty()#返回True,如果为空。
Queue.full() #返回True,如果full。
Queue.put(item, block= True, timeout= None)
将item放入队列中。如果可选的参数block为True,超时为None(默认),则在必要时阻塞,直到空闲插槽可用为止。如果超时是一个正数,它会在大多数超时秒内阻塞,如果在那个时间内没有空闲插槽,就会引发完全的异常。否则(block为false),如果立即可用空闲插槽,则在队列上放置一个项目,否则将引发完全异常(在这种情况下,超时将被忽略)。

Queue.put_nowait(item)
相当于Queue.put(item,False)。当队列满时,则

Queue.get(block=True, timeout=None)
从队列中移除并返回一个项。如果可选的args块为真,超时为None(默认),则在必要时阻塞,直到项目可用为止。如果超时是一个正数,它会在大多数超时秒内阻塞,如果在那个时间内没有可用的项,则会引发空的异常。否则(block为false),如果立即可用,则返回一个项,否则将引发空异常(在这种情况下,超时将被忽略)。

Queue.get_nowait()
相当于Queue.get(False)。

提供了两种方法来支持跟踪队列任务是否已由守护进程消费者线程完全处理。

Queue.task_done()
指示以前的队列任务已完成。用于队列消费线程。对于用于获取任务的每个get(),对task_done()的后续调用告诉队列,任务的处理是完整的。

如果一个join()当前正在阻塞,那么当所有的项目都被处理后,它将恢复(这意味着对已放入队列中的每一个项目都将收到一个task_done()调用)。

如果调用的次数多于队列中的项,则会产生一个ValueError错误。

Queue.join() block直到queue被消费完毕

常用队列:
FIFO = First in first out
LIFO = last in first out

import queue

q = queue.Queue(maxsize=3) #maxsize 设置大小,如果超过了maxsize,就会卡主, 先入先出

q.put("d1")
q.put("d2")
q.put("d3")

q.qsize()

print(q.get())
print(q.get())
print(q.get())

# 一个线程put,一个线程get
# 队列中没有数据的话,get就会卡主,等着数据进来
q.put(1)
q.put(2)
q.put(3)
q.get_nowait() #如果队列中没有数据,就会报错 ,可以通过q.qsize来控制,或者抓取异常
q.get(block=False) #效果同 get_nowait()
q.get(timeout=1)  #效果同 get_nowait()

q = queue.LifoQueue()   #后进先出队列
q.put(1)
q.put(2)
q.put(3)

print(q.get())
print(q.get())
print(q.get())

q = queue.PriorityQueue()  # 优先级队列,数字以小为优,
q.put((3,'hanyang'))
q.put((10,'alex'))
q.put((6,'wangsen'))

print(q.get())
print(q.get())
print(q.get())

3.11 生产者消费者模型

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

3.11.1 为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

3.11.2 什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

import threading,queue,time

q = queue.Queue(maxsize=10)  #定义队列大小

def Producer(name):
    count = 1
    while True:
        q.put("骨头%s"%count)
        print('生产了骨头%s'%count)
        count += 1
        time.sleep(0.5)


def Consumer(name):
    # while q.qsize() > 0:
    while True:
        print('[%s] 取到[%s],并且吃了它。。。。'%(name,q.get()))
        time.sleep(1)


p = threading.Thread(target=Producer,args=('Alex',))
c = threading.Thread(target=Consumer,args=('xxx',))
c1 = threading.Thread(target=Consumer,args=('bbb',))

p.start()
c.start()
c1.start()

3.12 timer

这个类表示应该在经过一定时间之后才运行的操作。
计时器的启动方式与线程一样,通过调用它们的start()方法。通过调用thecancel()方法,计时器可以被停止(在它的动作开始之前)。在执行其操作之前,计时器将等待的间隔可能与用户指定的间隔不完全相同。

def hello():
    print("hello, world")
 
t = Timer(30.0, hello)
t.start()  # after 30 seconds, "hello, world" will be printed

3.13 多线程使用场景

计算机上所有的io操作都不占用cpu,而所有计算例如1+1,这类操作是占用CPU的;

而python多线程不适合CPU密集操作型的任务,但是适合io密集型的任务;
io密集型:socketserver
cpu密集型:有大量的运算,

CPU操作密集型的任务,可以使用多进程来处理;

python的线程时操作系统的原生线程,Python的进程也是操作系统的原生进程,操作系统的原生的进程和原生线程都是由操作系统来维护的,python只不过是通过C语言的操作系统接口来启动进程和线程,操作系统本身没有GIL全局解释器锁;而进程之间的数据时独立的,因此也不需要锁机制;每个进程包含一个线程,就可以启动CPU核数的进程,来解决CPU操作密集型的任务。

3.14 线程池

3.14.1 过去

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import threadpool
import time

def sayhello (a):
    print("hello: "+a)
    time.sleep(2)

def main():
    seed=["a","b","c"]
    start=time.time()
    task_pool=threadpool.ThreadPool(5)
    requests=threadpool.makeRequests(sayhello,seed)
    for req in requests:
        task_pool.putRequest(req)
    task_pool.wait()
    end=time.time()
    time_m = end-start
    print("time: "+str(time_m))
    start1=time.time()
    for each in seed:
        sayhello(each)
    end1=time.time()
    print("time1: "+str(end1-start1))
main()

3.14.2 未来

#! /usr/bin/env python
# -*- coding: utf-8 -*-

from concurrent.futures import ThreadPoolExecutor
import time

def sayhello(a):
    print("hello: "+a)
    time.sleep(2)

def main():
    seed=["a","b","c"]
    start1=time.time()
    for each in seed:
        sayhello(each)
    end1=time.time()
    print("time1: "+str(end1-start1))
    start2=time.time()
    with ThreadPoolExecutor(3) as executor:
        for each in seed:
            executor.submit(sayhello,each)
    end2=time.time()
    print("time2: "+str(end2-start2))
    start3=time.time()
    with ThreadPoolExecutor(3) as executor1:
        executor1.map(sayhello,seed)
    end3=time.time()
    print("time3: "+str(end3-start3))

if __name__ == '__main__':
    main()

注意到一点:

concurrent.futures.ThreadPoolExecutor,在提交任务的时候,有两种方式,一种是submit()函数,另一种是map()函数,两者的主要区别在于:

2.1、map可以保证输出的顺序, submit输出的顺序是乱的

2.2、如果你要提交的任务的函数是一样的,就可以简化成map。但是假如提交的任务函数是不一样的,或者执行的过程之可能出现异常(使用map执行过程中发现问题会直接抛出错误)就要用到submit()

2.3、submit和map的参数是不同的,submit每次都需要提交一个目标函数和对应的参数,map只需要提交一次目标函数,目标函数的参数放在一个迭代器(列表,字典)里就可以。

3.14.3 现在

自己构建线程池

#! /usr/bin/env python
# -*- coding: utf-8 -*-

import threading
import Queue
import hashlib
import logging
from utils.progress import PrintProgress
from utils.save import SaveToSqlite


class ThreadPool(object):
    def __init__(self, thread_num, args):

        self.args = args
        self.work_queue = Queue.Queue()
        self.save_queue = Queue.Queue()
        self.threads = []
        self.running = 0
        self.failure = 0
        self.success = 0
        self.tasks = {}
        self.thread_name = threading.current_thread().getName()
        self.__init_thread_pool(thread_num)

    # 线程池初始化
    def __init_thread_pool(self, thread_num):
        # 下载线程
        for i in range(thread_num):
            self.threads.append(WorkThread(self))
        # 打印进度信息线程
        self.threads.append(PrintProgress(self))
        # 保存线程
        self.threads.append(SaveToSqlite(self, self.args.dbfile))

    # 添加下载任务
    def add_task(self, func, url, deep):
        # 记录任务,判断是否已经下载过
        url_hash = hashlib.new('md5', url.encode("utf8")).hexdigest()
        if not url_hash in self.tasks:
            self.tasks[url_hash] = url
            self.work_queue.put((func, url, deep))
            logging.info("{0} add task {1}".format(self.thread_name, url.encode("utf8")))

    # 获取下载任务
    def get_task(self):
        # 从队列里取元素,如果block=True,则一直阻塞到有可用元素为止。
        task = self.work_queue.get(block=False)

        return task

    def task_done(self):
        # 表示队列中的某个元素已经执行完毕。
        self.work_queue.task_done()

    # 开始任务
    def start_task(self):
        for item in self.threads:
            item.start()

        logging.debug("Work start")

    def increase_success(self):
        self.success += 1

    def increase_failure(self):
        self.failure += 1

    def increase_running(self):
        self.running += 1

    def decrease_running(self):
        self.running -= 1

    def get_running(self):
        return self.running

    # 打印执行信息
    def get_progress_info(self):
        progress_info = {}
        progress_info['work_queue_number'] = self.work_queue.qsize()
        progress_info['tasks_number'] = len(self.tasks)
        progress_info['save_queue_number'] = self.save_queue.qsize()
        progress_info['success'] = self.success
        progress_info['failure'] = self.failure

        return progress_info

    def add_save_task(self, url, html):
        self.save_queue.put((url, html))

    def get_save_task(self):
        save_task = self.save_queue.get(block=False)

        return save_task

    def wait_all_complete(self):
        for item in self.threads:
            if item.isAlive():
                # join函数的意义,只有当前执行join函数的线程结束,程序才能接着执行下去
                item.join()

# WorkThread 继承自threading.Thread
class WorkThread(threading.Thread):
    # 这里的thread_pool就是上面的ThreadPool类
    def __init__(self, thread_pool):
        threading.Thread.__init__(self)
        self.thread_pool = thread_pool

    #定义线程功能方法,即,当thread_1,...,thread_n,调用start()之后,执行的操作。
    def run(self):
        print (threading.current_thread().getName())
        while True:
            try:
                # get_task()获取从工作队列里获取当前正在下载的线程,格式为func,url,deep
                do, url, deep = self.thread_pool.get_task()
                self.thread_pool.increase_running()

                # 判断deep,是否获取新的链接
                flag_get_new_link = True
                if deep >= self.thread_pool.args.deep:
                    flag_get_new_link = False

                # 此处do为工作队列传过来的func,返回值为一个页面内容和这个页面上所有的新链接
                html, new_link = do(url, self.thread_pool.args, flag_get_new_link)

                if html == '':
                    self.thread_pool.increase_failure()
                else:
                    self.thread_pool.increase_success()
                    # html添加到待保存队列
                    self.thread_pool.add_save_task(url, html)

                # 添加新任务,即,将新页面上的不重复的链接加入工作队列。
                if new_link:
                    for url in new_link:
                        self.thread_pool.add_task(do, url, deep + 1)

                self.thread_pool.decrease_running()
                # self.thread_pool.task_done()
            except Queue.Empty:
                if self.thread_pool.get_running() <= 0:
                    break
            except Exception, e:
                self.thread_pool.decrease_running()
                # print str(e)
                break
posted @ 2018-05-26 14:26  耳_东  阅读(120)  评论(0)    收藏  举报