python-python基础9

本章内容:

  • paramiko模块
  • 多线程threading
    • 守护线程
    • 线程锁
    • 信号量
    • 事件  
  • 队列queue

 

一、paramiko模块(第三方模块,需要另外安装)

该模块基于SSH用于连接远程服务器并执行相关操作

SSHClient

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

import paramiko

# 创建SSH对象
ssh = paramiko.SSHClient()
# 允许连接不在know_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# 连接服务器
ssh.connect(hostname='192.168.242.132', port=22, username='root', password='225325')

# 执行命令,返回三个结果,stdin:标准输入,stdout:标准输出 或 stderr:标准错误
stdin, stdout, stderr = ssh.exec_command('df')
#标准输出和标准错误只会有一个有结果
res,err=stdout.read(),stderr.read()

# 获取命令结果
if res:
    result=res
else:
    result=err

print(result.decode())

# 关闭连接
ssh.close()

SFTPClient

用于连接远程服务器并执行上传下载

基于用户名密码上传下载

import paramiko
transport = paramiko.Transport(('192.168.242.129', 22))
transport.connect(username='root', password='225325')
sftp = paramiko.SFTPClient.from_transport(transport)
# 将location.py 上传至服务器 /tmp/test.py
#sftp.put('test.py', '/tmp/test_from_win')
# 将remove_path 下载到本地 local_path
sftp.get('/tmp/bs.yaml', 'fromlinux.txt')

transport.close()

 

二。进程与线程

什么是进程(process)?

程序的执行实例称为进程。

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

程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。

在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。

有了进程为什么还要线程?

进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上:

  • 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。

  • 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。

例如,我们在使用qq聊天, qq做为一个独立进程如果同一时间只能干一件事,那他如何实现在同一时刻 即能监听键盘输入、又能监听其它人给你发的消息、同时还能把别人发的消息显示在屏幕上呢?你会说,操作系统不是有分时么?但我的亲,分时是指在不同进程间的分时呀, 即操作系统处理一会你的qq任务,又切换到word文档任务上了,每个cpu时间片分给你的qq程序时,你的qq还是只能同时干一件事呀。

再直白一点, 一个操作系统就像是一个工厂,工厂里面有很多个生产车间,不同的车间生产不同的产品,每个车间就相当于一个进程,且你的工厂又穷,供电不足,同一时间只能给一个车间供电,为了能让所有车间都能同时生产,你的工厂的电工只能给不同的车间分时供电,但是轮到你的qq车间时,发现只有一个干活的工人,结果生产效率极低,为了解决这个问题,应该怎么办呢?。。。。没错,你肯定想到了,就是多加几个工人,让几个人工人并行工作,这每个工人,就是线程!

 

什么是线程(thread)?

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务

线程是执行上下文,它是CPU执行指令流所需的所有信息。

CPU给您一种错觉,即它同时进行多个计算。它通过在每次计算上花费一些时间来做到这一点。之所以可以这样做,是因为它具有每次计算的执行上下文。就像您可以与朋友共享一本书一样,许多任务可以共享一个CPU。

从技术上讲,执行上下文(因此是线程)由CPU寄存器的值组成。

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

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

 

进程与线程的区别

  1. 线程共享创建它的进程的地址空间; 进程具有自己的地址空间。
  2. 线程可以直接访问其进程的数据段。 进程具有其父流程数据段的副本。
  3. 线程可以直接与其进程中的其他线程通信。 进程必须使用进程间通信与同级进程进行通信。
  4. 新线程很容易创建; 新进程需要复制父进程。
  5. 线程可以对同一进程的线程进行相当多的控制。 进程只能控制子进程。
  6. 主线程的更改(取消,优先级更改等)可能会影响该进程其他线程的行为; 对父进程的更改不会影响子进程。

 

简单的多线程示例:

import threading,time

def run(n):
    print("task",n)
    time.sleep(2)

t1=threading.Thread(target=run,args=("t1",))
t2=threading.Thread(target=run,args=("t2",))

t1.start()
t2.start()

用多线程并行执行run这个任务的时候,可以同时打印出"task t1"和"task t2",而不需要打印出"task t1"后等两秒后在执行run(t2)

继承式调用:

import threading,time

class MyThread(threading.Thread):
    def __init__(self,n):
        super(MyThread,self).__init__()
        self.n=n
    def run(self):   #定义每个线程要运行的函数,函数名不能改
        print("running ",self.n)

t1=MyThread("t1")
t2=MyThread("t2")

t1.start()
t2.start()

 

计算多个线程全部执行完所耗费的时间:

import threading,time

def run(n):
    print("task",n)
    time.sleep(2)

start_time=time.time()
t_obj=[]
for i in range(50):    #用循环的方式一次启动50个线程
    t=threading.Thread(target=run,args=("t---%s"%i,))
    t.start()
    t_obj.append(t)

print("线程全部启动")
for r in t_obj:
    r.join()   #join的意思是等待线程的执行结果后再往下执行

print("全部线程执行完所耗费的时间:",time.time()-start_time)

执行这个python文件是一个主线程,在这个主线程中生成50个子线程,它们都是独立运行,互不影响的,也就是说,每个线程启动后,不会等这个线程执行完毕才开始执行下一个进程。所以把每个线程启动后,加进t_obj列表中,同时把这个列表中的每个线程实例join(),这样就可以让50个线程都执行完后,最后执行主线程的print(),计算时间。

 

threading.current_thread() : 查看当前运行这条命令的线程类型(主线程或子线程)

threading.active_count() : 查看当前线程个数

threading.enumerate:返回一个包含正在运行的线程的list,正在运行的线程指的是线程启动后,结束前的状态
threading.activeCount:返回正在运行的线程数量,效果跟len(threading.enumerate) 相同
thr.setName:给线程设置名字
thr.getName:得到线程的名字

 

守护线程:

守护线程 setDaemon(True)
            如果在程序中将子线程设置成守护线程,则子线程会在主线程结束的时候自动退出
            一般认为,守护线程不重要或者不允许离开主线程独立运行
import threading
import time

def run(n):
    print("task",n)
    time.sleep(2)

start_time=time.time()
t_obj=[]
for i in range(50):    #用循环的方式一次启动50个线程
    t=threading.Thread(target=run,args=("t---%s"%i,))
    t.setDaemon(True)   #把当前线程设置为守护线程
    t.start()
    t_obj.append(t)

print("线程全部启动")
#for r in t_obj:
#    r.join()   #join的意思是等待线程的执行结果

print("全部线程执行完所耗费的时间:",time.time()-start_time)

把50个子线程都设置为守护线程后,只要主线程执行完退出,不管守护线程有没有执行完,都会跟着退出

 

Python GIL(Global Interpreter Lock) 

在CPython中,全局解释器锁(即GIL)是一个互斥体,可以防止多个本机线程一次执行Python字节码。 锁定是必要的,主要是因为CPython的内存管理不是线程安全的。 (但是,由于存在GIL,因此其他功能也逐渐依赖于它所执行的保证。)无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行

 

python多线程不适合cpu密集型任务,只适合IO密集型任务。

因为python的多线程是假的多线程,即同一时间只能在一颗cpu上以上下文切换的方式来执行任务,如果是cpu密集型任务,需要大量的cpu运算时间,而python多线程的上下文切换也会耗费很多cpu时间,所以cpu密集型任务还不如用单线程。

那cpu密集型任务该如何解决呢?可以用多进程的方式,下一章讲。

 

线程锁(互斥锁Mutex)

一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果多个线程同时要修改同一份数据,可能会有问题。

例:2个线程同时修改sum这个变量

import threading

sum = 0
loopSum = 1000000

def myAdd():
    global sum,loopSum
    for i in range(loopSum):
        sum +=1

def myMinu():
    global sum,loopSum
    for i in range(loopSum):
        sum-=1

if __name__ == '__main__':
    print("Starting.The sum is {0}".format(sum))

    #开始多线程实例
    t1=threading.Thread(target=myAdd,args=())
    t2=threading.Thread(target=myMinu,args=())

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print("Done.The sum is {0}.".format(sum))
因为两个线程同时用到共同的变量sum,所以会干扰结果

原因是,当一个线程在修改一个变量时,比如sum+1,还没修改完,但是时间到了,被要求释放GIL,那么另外一个线程进来修改这个变量比如sum-1,修改完成写进内存,此时sum=-1,然后刚刚没执行完的线程sum+1继续执行,执行完后把结果存进变量,此时sum=1,但是这个结果是一个错误的结果,因为两个线程都执行完的话sum应该等于0.

为了避免这种情况出现,我们应该在每个线程修改变量之前加一个线程锁,当这个线程修改这个变量的时候,其它线程不能操作这个变量,当那个线程修改完成,释放锁以后,另一个线程才能继续操作这个变量。看一个加锁的示例:

import threading

sum = 0
loopSum = 100000

def myAdd():
    global sum,loopSum
    for i in range(loopSum):
        lock.acquire()    #申请锁
        sum +=1
        lock.release()    #释放锁

def myMinu():
    global sum,loopSum
    for i in range(loopSum):
        lock.acquire()
        sum-=1
        lock.release()

lock=threading.Lock()     #生成一个线程锁实例
if __name__ == '__main__':
    print("Starting.The sum is {0}".format(sum))

    #开始多线程实例
    t1=threading.Thread(target=myAdd,args=())
    t2=threading.Thread(target=myMinu,args=())

    t1.start()
    t2.start()

    t1.join()
    t2.join()

    print("Done.The sum is {0}.".format(sum))

 

Semaphore(信号量)

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

import threading,time

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

semaphore=threading.BoundedSemaphore(3)   #生成一个信号量实例,最多允许3个线程同时执行
for i in range(10):
    t=threading.Thread(target=run,args=(i,))
    t.start()

while threading.active_count()!=1:
    pass
else:
    print("线程执行完毕")

 

Events(事件)

事件是一个简单的同步对象。

该事件表示一个内部标志,并且线程
可以等待设置标志,或者自己设置或清除标志。

event= threading.Event()

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

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

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

import threading,time

event=threading.Event()
def Light():
    count=1
    event.set()   #绿灯
    while True:
        if count>5 and count<=10:
            event.clear()   #清空标志位,红灯
            print("\033[41;1m红灯亮\033[0m")
        elif count>10:
            event.set()
            print("\033[36;42;1m绿灯亮\033[0m")
            count=1
        else:
            print("\033[36;42;1m绿灯亮\033[0m")
        count+=1
        time.sleep(1)

def car(name):
    while True:
        if event.is_set():
            print("绿灯,%s开车"%name)
            time.sleep(1)
        else:
            print("红灯,%s停车"%name)
            event.wait()    #等待标志位被设定
            print("绿灯亮了,开车")

light=threading.Thread(target=Light)
light.start()
car1=threading.Thread(target=car,args=("马自达",))
car1.start()
car2=threading.Thread(target=car,args=("奥迪",))
car2.start()

不用event实现:

import threading,time

def Light():
    count=1
    global redlight
    redlight = True
    while True:
        if count>5 and count<=10:
            print("\033[36;42;1m绿灯亮\033[0m")
            redlight = False
        elif count>10:
            print("\033[41;1m红灯亮\033[0m")
            redlight = True
            count=0
        else:
            print("\033[41;1m红灯亮\033[0m")
            redlight = True
        count+=1
        time.sleep(1)

def car(name):
    while True:
        if redlight is True:
            print("红灯,停车")
            time.sleep(1)
        else:
            print("绿灯,开车")
            time.sleep(1)

light=threading.Thread(target=Light)
light.start()
car1=threading.Thread(target=car,args=("car1",))
car1.start()

 

queue队列 

主要作用

解耦,使程序实现松耦合(一个模块修改不会影响其他模块)

提高效率

队列与列表的关系

队列中数据只有一份,取出就没有了,区别于列表,列表数据取出只是复制了一份

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

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

优先级队列示例:

import queue

q=queue.PriorityQueue(maxsize=10)
#用优先级队列的方式创建队列,传一个元组进队列里,队列会根据元组中第一个数字来排序
q.put((36,"zhihao"))
q.put((7,"zhang"))
q.put((5,"zzh"))
q.put((55,"xiaodu"))
q.put((1,"jehu"))

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

执行结果:

(1, 'jehu')
(5, 'zzh')
(7, 'zhang')
(36, 'zhihao')
(55, 'xiaodu')

优先级队列的构造函数。 maxsize是一个整数,用于设置可以放入队列中的项目数的上限。 一旦达到此大小,插入将被阻塞,直到消耗队列项目。 如果maxsize小于或等于零,则队列大小为无限。

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

maxsize

maxsize 是实例化 Queue 类时的一个参数,默认为 0

Queue(maxsize=0) 可以控制队列中数据的容量

put

Queue.put(block=True, timeout=None)

block 用于设置是否阻塞, timeout 用于设置阻塞时等待时长

put_nowait() = put(block=False)

阻塞

当队列满了之后,put 就会阻塞,一直等待队列不再满时向里面添加数据

不阻塞

当队列满了之后,如果设置 put 不阻塞,或者等待时长到了之后会报错:queue.Full

get

Queue.get(block=True, timeout=None)

get_nowait() = get(block=False)

阻塞

当队列空了之后,get 就会阻塞,一直等待队列中有数据后再获取数据

不阻塞

当队列空了之后,如果设置 get 不阻塞,或者等待时长到了之后会报错:_queue.Empty

full & empty

Queue.empty()/Queue.full() 用于判断队列是否为空、满

尽量使用 qsize 代替

qsize

Queue.qsize() 用于获取队列中大致的数据量

注意:在多线程的情况下不可靠

因为在获取 qsize 时,其他线程可能又对队列进行操作了

 

生产者消费者模型:

import threading,time,queue

q=queue.Queue(maxsize=10)
def Produce():
    count=1
    while True:
        q.put("[apple%s]"%count)
        print("生产[apple%s]"%count)
        count+=1
        time.sleep(1)

def consumer(name):
    while True:
        a=q.get()
        print("%s 吃了%s"%(name,a))
        time.sleep(1)

p1=threading.Thread(target=Produce)
p1.start()
c1=threading.Thread(target=consumer,args=("zzh",))
c1.start()
c2=threading.Thread(target=consumer,args=("jehu",))
c2.start()
posted @ 2020-03-07 22:56  jehuzzh  阅读(135)  评论(0编辑  收藏  举报