python学习笔记_week9

一、paramiko模块

SSHClient,用于连接远程服务器并执行基本命令

基于用户名密码连接:

ssh执行命令:stdin,stdout,sterr:标准输入、输出、错误

 1 import paramiko
 2 # 创建SSH对象
 3 ssh = paramiko.SSHClient()
 4 # 允许连接不在know_hosts文件中的主机
 5 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 6 # 连接服务器
 7 ssh.connect(hostname='c1.salt.com', port=22, username='wupeiqi', password='123')
 8 # 执行命令
 9 stdin, stdout, stderr = ssh.exec_command('df')
10 # 获取命令结果
11 res,err=stdout.read(),stderr.read()
12 result = res if res else err
13 print(result.decode())
14 # 关闭连接
15 ssh.close()
ssh

传文件:Linux上:scp(基于ssh的ftp协议) r(目录) p(权限)

python模拟实现:SFTPClient,用于连接远程服务器并执行上传下载

基于用户名密码上传下载:

 1 import paramiko
 2 #建立一个链接
 3 transport = paramiko.Transport(('hostname', 22))
 4 transport.connect(username='wupeiqi', password='123')
 5 sftp = paramiko.SFTPClient.from_transport(transport)
 6 # 将location.py 上传至服务器 /tmp/test.py
 7 sftp.put('/tmp/location.py', '/tmp/test.py')
 8 # 将remove_path 下载到本地 local_path
 9 sftp.get('remove_path', 'local_path')
10 transport.close()
ssh_sftp

二、密钥

直接写用户名密码不安全(明文的)--->不用输密码来连接(用密钥)

RSA—非对称密钥验证

公钥(public key)、私钥(private key) ---成对出现。私钥自己拿着,公钥给对方。ssh-keygen:

将公钥拷贝到对方的.ssh下

  设置读写执行权限

或者用命令拷贝

python上模拟实现:

将私钥copy过来

 1 import paramiko
 2 private_key = paramiko.RSAKey.from_private_key_file('id_rsa31.txt')#指定你的公钥在哪里
 3 # 创建SSH对象
 4 ssh = paramiko.SSHClient()
 5 # 允许连接不在know_hosts文件中的主机
 6 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
 7 # 连接服务器
 8 ssh.connect(hostname='10.0.0.41', port=52113, username='gongli', pkey=private_key)
 9 # 执行命令
10 stdin, stdout, stderr = ssh.exec_command('df')
11 # 获取命令结果
12 result = stdout.read()
13 # 关闭连接
14 ssh.close()
ssh_rsa

三、进程与线程

计算机中,cpu负责运算。线程是操作系统能够进行运算调度的最小单位(线程可简单理解为一串指令的集合)。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

进程:qq 要以一个整体的形式暴露给操作系统管理,里面包含对各种资源的调用,内存的管理,网络接口的调用等...

           对各种资源管理的集合就可以称为进程。

进程要操作cpu,必须先创建一个线程。(进程相当于屋子这个整体,线程相当于屋子里的人)

线程A thread is an execution context(上下文), which is all the information a CPU needs to execute a stream of instructions.

Suppose you're reading a book, and you want to take a break right now, but you want to be able to come back and resume reading from the exact point where you stopped. One way to achieve that is by jotting down the page number, line number, and word number. So your execution context for reading a book is these 3 numbers.

If you have a roommate, and she's using the same technique, she can take the book while you're not using it, and resume reading from where she stopped. Then you can take it back, and resume it from where you were.

Threads work in the same way. A CPU is giving you the illusion(幻觉) that it's doing multiple computations at the same time. It does that by spending a bit of time on each computation. It can do that because it has an execution context for each computation. Just like you can share a book with your friend, many tasks can share a CPU.

On a more technical level, an execution context (therefore a thread) consists of the values of the CPU's registers(寄存器).

Last: threads are different from processes(进程). A thread is a context of execution, while a process is a bunch of(一堆) resources associated(相关的) with a computation. A process can have one or many threads.

Clarification: the resources associated with a process include memory pages (all the threads in a process have the same view of the memory---所有在同一个进程里的线程共享同一块内存空间), file descriptors (e.g., open sockets), and security credentials (e.g., the ID of the user who started the process).

进程:An executing instance of a program is called a process.

Each process provides the resources needed to execute a program. A process has a virtual address space, executable code, open handles to system objects, a security context, a unique(唯一的) process identifier(进程标识符,pid), environment variables, a priority class(优先级类), minimum and maximum working set sizes, and at least one thread of execution. Each process is started with a single thread, often called the primary thread(主线程---进程中的第一个线程), but can create additional threads from any of its threads.(子线程可以继续创建子线程并且相互独立)

进程与线程的区别?---进程还是线程快(运行没有可比性),但是线程启动快)

  1. Threads share the address space of the process that created it; processes have their own address space.线程共享内存空间,进程的内存是独立的
  2. Threads have direct access to the data segment of its process; processes have their own copy of the data segment of the parent process.
  3. Threads can directly communicate with other threads of its process; processes must use interprocess(中间进程) communication to communicate with sibling(兄弟,姐妹) processes.同一个进程的线程之间可以直接交流,两个进程想通信,必须通过一个中间代理来实现
  4. New threads are easily created; new processes require duplication of the parent process.创建新线程很简单,创建新进程需要对其父进程进行一次克隆
  5. Threads can exercise considerable control over threads of the same process; processes can only exercise control over child processes.一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程
  6. Changes to the main thread (cancellation, priority change, etc.) may affect the behavior of the other threads of the process; changes to the parent process does not affect child processes.

Python GIL(Global Interpreter Lock)  

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行,擦。。。,那这还叫什么多线程呀?莫如此早的下结结论,听我现场讲。

首先需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL

这篇文章透彻的剖析了GIL对python多线程的影响,强烈推荐看一下:http://www.dabeaz.com/python/UnderstandingGIL.pdf 

四、Python threading模块

线程有2种调用方式。1、直接调用。2、继承式调用。

 1 import threading
 2 import time
 3 def run(n):
 4     print("task",n)
 5     time.sleep(2)
 6     print("task done",n,threading.current_thread())
 7 start_time=time.time()
 8 t_objs=[] #存线程实例
 9 for i in range(50):
10     t=threading.Thread(target=run,args=("t-%s"%i,)) #启动线程
11     t.start()
12     t_objs.append(t) #为了不阻塞后面线程的启动,不在这里join,先放到一个列表里
13 # for t in t_objs: #循环线程实例列表,等待所有线程执行完毕
14 #     t.join()
15 print("all threads has finished...",threading.current_thread(),threading.active_count())#current是否为主线程,active线程运行的个数
16 print("cost:",time.time()-start_time)
threadding_ex1
 1 import threading
 2 import time
 3 class MyThread(threading.Thread):
 4     def __init__(self,n,sleep_time):
 5         super(MyThread,self).__init__()
 6         self.n=n
 7         self.sleep_time=sleep_time
 8     def run(self): #定义每个线程要运行的函数
 9         print("running task",self.n)
10         time.sleep(self.sleep_time)
11         print("task done",self.n)
12 t1=MyThread("t1",2)
13 t2=MyThread("t2",4)
14 t1.start()
15 t2.start()
16 t1.join() #等待t1执行完
17 t2.join()
18 print("main thread...")
threading_ex2

 Join&deamon:

 1 import threading
 2 import time
 3 def run(n):
 4     print("task",n)
 5     time.sleep(2)
 6     print("task done",n,threading.current_thread())
 7 start_time=time.time()
 8 t_objs=[] #存线程实例
 9 for i in range(50):
10     t=threading.Thread(target=run,args=("t-%s"%i,)) #启动线程
11     t.setDaemon(True) #把当前线程设置为守护线程,一定要在start之前
12     t.start()
13     t_objs.append(t) #为了不阻塞后面线程的启动,不在这里join,先放到一个列表里
14 # for t in t_objs: #循环线程实例列表,等待所有线程执行完毕
15 #     t.join()
16 print("all threads has finished...",threading.current_thread(),threading.active_count())#current是否为主线程,active线程运行的个数
17 print("cost:",time.time()-start_time)
threading_ex3_daemon

五、线程锁(互斥锁Mutex)

一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?

正常来讲,这个num结果应该是1000, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是1000,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行加1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=0这个初始变量交给cpu去运算,当A线程去处完的结果是1,但此时B线程运算完的结果也是1,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是1。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。 *注:不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁

加锁版

 1 import threading
 2 import time
 3 def run(n):
 4     lock.acquire()#获取一把锁(用户锁)
 5     global  num
 6     num+=1
 7     time.sleep(1)
 8     lock.release() #释放用户锁
 9 lock=threading.Lock() #生成全局锁
10 num=0
11 t_objs=[] #存线程实例
12 for i in range(1000):
13     t=threading.Thread(target=run,args=("t-%s"%i,)) #启动线程
14     t.start()
15     t_objs.append(t)
16 for t in t_objs: #循环线程实例列表,等待所有线程执行完毕
17     t.join()
18 print("all threads has finished...",threading.current_thread(),threading.active_count())
19 print("num:",num)
threading_ex3_lock

GIL VS Lock 

机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 ,具体我们通过下图来看一下+配合我现场讲给大家,就明白了。

那你又问了, 既然用户程序已经自己有锁了,那为什么C python还需要GIL呢?加入GIL主要的原因是为了降低程序的开发的复杂度,比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题,  这可以说是Python早期版本的遗留问题。

RLock(递归锁)

说白了就是在一个大锁中还要再包含子锁(用的不多

 1 import threading,time
 2 def run1():
 3     print("grab the first part data")
 4     lock.acquire()
 5     global num
 6     num +=1
 7     lock.release()
 8     return num
 9 def run2():
10     print("grab the second part data")
11     lock.acquire()
12     global  num2
13     num2+=1
14     lock.release()
15     return num2
16 def run3():
17     lock.acquire()
18     res = run1()
19     print('--------between run1 and run2-----')
20     res2 = run2()
21     lock.release()
22     print(res,res2)
23 num,num2 = 0,0
24 lock = threading.RLock() #递归锁 用Lock的话死循环了
25 for i in range(1):
26     t = threading.Thread(target=run3)
27     t.start()
28 while threading.active_count() != 1:
29     print(threading.active_count())
30 else:
31     print('----all threads done---')
32     print(num,num2)
rlock

六、

Semaphore(信号量)

互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去,出来几个(不大于3)进去几个

 1 import threading, time
 2 def run(n):
 3     semaphore.acquire()
 4     time.sleep(1)
 5     print("run the thread: %s\n" % n)
 6     semaphore.release()
 7 if __name__ == '__main__':
 8     semaphore = threading.BoundedSemaphore(5)  # 最多允许5个线程同时运行
 9     for i in range(20):
10         t = threading.Thread(target=run, args=(i,))
11         t.start()
12 while threading.active_count() != 1:
13     pass  # print threading.active_count()
14 else:
15     print('----all threads done---')
信号量

Timer  

This class represents an action that should be run only after a certain amount of time has passed 

Timers are started, as with threads, by calling their start() method. The timer can be stopped (before its action has begun) by calling thecancel() method. The interval the timer will wait before executing its action may not be exactly the same as the interval specified by the user.

1 import threading
2 def hello():
3     print("hello, world")
4 t = threading.Timer(30, hello)
5 t.start()  # after 30 seconds, "hello, world" will be printed
Timer

Events(事件)

An event is a simple synchronization object;

the event represents an internal flag, and threads
can wait for the flag to be set, or set or clear the flag themselves.

event = threading.Event()

# a client thread can wait for the flag to be set
event.wait()

# a server thread can set or reset it
event.set()
event.clear()
If the flag is set, the wait method doesn’t do anything.标志位设定了,代表绿灯,直接通行

If the flag is cleared, wait will block until it becomes set again.标志位被清空,代表红灯,wait等待变绿灯
Any number of threads may wait for the same event.很多车等一个红灯

通过Event来实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红灯停,绿灯行的规则。

 1 import time
 2 import threading
 3 event = threading.Event()
 4 def lighter():
 5     count = 0
 6     event.set() #先设置绿灯
 7     while True:
 8         if count >5 and count < 10: #改成红灯
 9             event.clear() #把标志位清了
10             print("\033[41;1mred light is on....\033[0m")
11         elif count >10:
12             event.set() #变绿灯
13             count = 0
14         else:
15             print("\033[42;1mgreen light is on....\033[0m")
16         time.sleep(1)
17         count +=1
18 def car(name):
19     while True:
20         if event.is_set(): #判断是否设置了标志位,代表绿灯
21             print("[%s] running..."% name )
22             time.sleep(1)
23         else:
24             print("[%s] sees red light , waiting...." %name)
25             event.wait()
26             print("\033[34;1m[%s] green light is on, start going...\033[0m" %name)
27 light = threading.Thread(target=lighter,)
28 light.start()
29 car1 = threading.Thread(target=car,args=("Tesla",))
30 car1.start()
红绿灯
 1 import threading,time
 2 import random
 3 def light():
 4     if not event.isSet():
 5         event.set() #wait就不阻塞 #绿灯状态
 6     count = 0
 7     while True:
 8         if count < 10:
 9             print('\033[42;1m--green light on---\033[0m')
10         elif count <13:
11             print('\033[43;1m--yellow light on---\033[0m')
12         elif count <20:
13             if event.isSet():
14                 event.clear()
15             print('\033[41;1m--red light on---\033[0m')
16         else:
17             count = 0
18             event.set() #打开绿灯
19         time.sleep(1)
20         count +=1
21 def car(n):
22     while 1:
23         time.sleep(random.randrange(10))
24         if  event.isSet(): #绿灯
25             print("car [%s] is running.." % n)
26         else:
27             print("car [%s] is waiting for the red light.." %n)
28 if __name__ == '__main__':
29     event = threading.Event()
30     Light = threading.Thread(target=light)
31     Light.start()
32     for i in range(3):
33         t = threading.Thread(target=car,args=(i,))
34         t.start()
红绿灯改进

这里还有一个event使用的例子,员工进公司门要刷卡, 我们这里设置一个线程是“门”, 再设置几个线程为“员工”,员工看到门没打开,就刷卡,刷完卡,门开了,员工就可以通过。

 1 import threading
 2 import time
 3 import random
 4 def door():
 5     door_open_time_counter = 0
 6     while True:
 7         if door_swiping_event.is_set():
 8             print("\033[32;1mdoor opening....\033[0m")
 9             door_open_time_counter +=1
10         else:
11             print("\033[31;1mdoor closed...., swipe to open.\033[0m")
12             door_open_time_counter = 0 #清空计时器
13             door_swiping_event.wait()
14         if door_open_time_counter > 3:#门开了已经3s了,该关了
15             door_swiping_event.clear()
16         time.sleep(0.5)
17 def staff(n):
18     print("staff [%s] is comming..." % n )
19     while True:
20         if door_swiping_event.is_set():
21             print("\033[34;1mdoor is opened, passing.....\033[0m")
22             break
23         else:
24             print("staff [%s] sees door got closed, swipping the card....." % n)
25             print(door_swiping_event.set())
26             door_swiping_event.set()
27             print("after set ",door_swiping_event.set())
28         time.sleep(0.5)
29 door_swiping_event  = threading.Event() #设置事件
30 door_thread = threading.Thread(target=door)
31 door_thread.start()
32 for i in range(5):
33     p = threading.Thread(target=staff,args=(i,))
34     time.sleep(random.randrange(3))
35     p.start()
员工刷卡

七、queue队列 (作用 1.解耦.2.提高效率)

与列表区别:列表元素取走之后数据还有(相当于复制了一份),但队列取走之后数据就没有了

queue is especially useful in threaded programming when information must be exchanged safely between multiple threads.

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

Constructor for a priority queue. maxsize is an integer that sets the upperbound limit on the number of items that can be placed in the queue. Insertion will block once this size has been reached, until queue items are consumed. If maxsize is less than or equal to zero, the queue size is infinite.

The lowest valued entries are retrieved first (the lowest valued entry is the one returned by sorted(list(entries))[0]). A typical pattern for entries is a tuple in the form: (priority_number, data).

exception queue.Empty

Exception raised when non-blocking get() (or get_nowait()) is called on a Queue object which is empty.

exception queue.Full

Exception raised when non-blocking put() (or put_nowait()) is called on a Queue object which is full.

Queue.qsize()
Queue.empty() #return True if empty  
Queue.full() # return True if full 
Queue.put(itemblock=Truetimeout=None)

Put item into the queue. If optional args block is true and timeout is None (the default), block if necessary until a free slot is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Full exception if no free slot was available within that time. Otherwise (block is false), put an item on the queue if a free slot is immediately available, else raise the Full exception (timeout is ignored in that case).

Queue.put_nowait(item)

Equivalent to put(item, False).

Queue.get(block=Truetimeout=None)

Remove and return an item from the queue. If optional args block is true and timeout is None (the default), block if necessary until an item is available. If timeout is a positive number, it blocks at most timeout seconds and raises the Empty exception if no item was available within that time. Otherwise (block is false), return an item if one is immediately available, else raise the Empty exception (timeout is ignored in that case).

Queue.get_nowait()

Equivalent to get(False).

Two methods are offered to support tracking whether enqueued tasks have been fully processed by daemon consumer threads.

Queue.task_done()

Indicate that a formerly enqueued task is complete. Used by queue consumer threads. For each get() used to fetch a task, a subsequent call to task_done() tells the queue that the processing on the task is complete.

If a join() is currently blocking, it will resume when all items have been processed (meaning that a task_done() call was received for every item that had been put() into the queue).

Raises a ValueError if called more times than there were items placed in the queue.

Queue.join() block直到queue被消费完毕
 1 import queue
 2 q = queue.PriorityQueue()
 3 q.put((-1,"chenronghua"))
 4 q.put((3,"hanyang"))
 5 q.put((10,"alex"))
 6 q.put((6,"wangsen"))
 7 print(q.get())
 8 print(q.get())
 9 print(q.get())
10 print(q.get())
11 # q  = queue.LifoQueue()
12 # q.put(1)
13 # q.put(2)
14 # q.put(3)
15 # print(q.get())
16 # print(q.get())
17 # print(q.get())
queue

八、生产者消费者模型

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

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

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

什么是生产者消费者模式

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

 1 import threading,time
 2 import queue
 3 q = queue.Queue(maxsize=10)
 4 def Producer(name):
 5     count = 1
 6     while True:
 7         q.put("骨头%s" % count)
 8         print("生产了骨头",count)
 9         count +=1
10         time.sleep(0.1)
11 def  Consumer(name):
12     #while q.qsize()>0:
13     while True:
14         print("[%s] 取到[%s] 并且吃了它..." %(name, q.get()))
15         time.sleep(1)
16 p = threading.Thread(target=Producer,args=("Alex",))
17 c = threading.Thread(target=Consumer,args=("ChengRonghua",))
18 c1 = threading.Thread(target=Consumer,args=("王森",))
19 p.start()
20 c.start()
21 c1.start()
producer_consumer
 1 import time,random
 2 import queue,threading
 3 q = queue.Queue()
 4 def Producer(name):
 5   count = 0
 6   while count <20:
 7     time.sleep(random.randrange(3))
 8     q.put(count)
 9     print('Producer %s has produced %s baozi..' %(name, count))
10     count +=1
11 def Consumer(name):
12   count = 0
13   while count <20:
14     time.sleep(random.randrange(4))
15     if not q.empty():
16         data = q.get()
17         print(data)
18         print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data))
19     else:
20         print("-----no baozi anymore----")
21     count +=1
22 p1 = threading.Thread(target=Producer, args=('A',))
23 c1 = threading.Thread(target=Consumer, args=('B',))
24 p1.start()
25 c1.start()
包子

作业

题目:简单主机批量管理工具

需求:

  1. 主机分组
  2. 主机信息配置文件用configparser解析
  3. 可批量执行命令、发送文件,结果实时返回,执行格式如下 
    1. batch_run  -h h1,h2,h3   -g web_clusters,db_servers    -cmd  "df -h" 
    2. batch_scp   -h h1,h2,h3   -g web_clusters,db_servers  -action put  -local test.py  -remote /tmp/ 
  4. 主机用户名密码、端口可以不同
  5. 执行远程命令使用paramiko模块
  6. 批量命令需使用multiprocessing并发

 

 

posted on 2017-11-06 17:12  我很好u 阅读( ...) 评论( ...) 编辑 收藏

转载于:https://www.cnblogs.com/jyh-py-blog/p/7794132.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值