本篇内容:

1.paramiko模块使用

2.进程、线程简介

3.python调用线程的方法

4.join - 等待线程执行

5.守护线程

6.GIL - 全局解释器锁

7.互斥锁

8.信号量

9.事件

10.队列



一、paramiko模块使用

1.paramiko模块简介

  paramiko是一个基于SSH用于连接远程服务器并执行相关操作(SSHClient和SFTPClinet,即一个是远程连接,一个是上传下载服务),使用该模块可以对远程服务器进行命令或文件操作,值得一说的是,fabric和ansible内部的远程管理就是使用的paramiko来现实。


2.使用paramiko模块做SSHClient:用于连接远程服务器并执行基本命令

server上要启动ssh程序


①SSHClient没有封装Transport的用法

import paramiko


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

# 允许连接不在~/.ssh/known_hosts文件中的主机
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# 连接服务器
ssh.connect(hostname='c1.salt.com', port=22, username='root', password='123')

# 执行命令,不要执行top之类的在不停的刷新的命令
stdin, stdout, stderr = ssh.exec_command('df')

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

# 获取的命令结果是bytes类型
print(result.decode(encoding="utf-8"))

# 关闭连接
ssh.close()


②SSHClient封装Transport的用法

import paramiko


transport = paramiko.Transport(('hostname', 22))    # 建立连接

transport.connect(username='wupeiqi', password='123')    # 建立连接

# 创建SSH对象
ssh = paramiko.SSHClient()    # SSHClient是定义怎么传输命令、怎么交互文件
ssh._transport = transport

# 执行命令,不要执行top之类的在不停的刷新的命令
stdin, stdout, stderr = ssh.exec_command('df')

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

# 获取的命令结果是bytes类型
print(result.decode(encoding="utf-8"))

# 关闭连接
transport.close()


③基于ssh免密登入的私钥连接

import paramiko


# 指定使用ssh免密登入的私钥
private_key = paramiko.RSAKey.from_private_key_file('/home/auto/.ssh/id_rsa')

transport = paramiko.Transport(('hostname', 22))
transport.connect(username='root', pkey=private_key)

ssh = paramiko.SSHClient()
ssh._transport = transport

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

transport.close()


3.使用paramiko模块做SFTPClient:用于连接远程服务器并执行上传下载

server上要启动ssh程序


①基于用户名密码上传下载,SFTPClient封装Transport的用法

import paramiko


transport = paramiko.Transport(('hostname', 22))  # 建立连接

transport.connect(username='root', password='123')  # 建立连接

# 创建sftp对象
sftp = paramiko.SFTPClient.from_transport(transport)  # SFTPClient是定义怎么传输文件、怎么交互文件

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

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

# 关闭连接
transport.close()


②基于ssh免密登入的私钥上传下载

import paramiko


# 指定使用ssh免密登入的私钥
private_key = paramiko.RSAKey.from_private_key_file('/home/auto/.ssh/id_rsa')
 
transport = paramiko.Transport(('hostname', 22))
transport.connect(username='root', pkey=private_key )
 
sftp = paramiko.SFTPClient.from_transport(transport)
# 将location.py 上传至服务器 /tmp/test.py
sftp.put('/tmp/location.py', '/tmp/test.py')
# 将remove_path 下载到本地 local_path
sftp.get('remove_path', 'local_path')
 
transport.close()



二、进程、线程简介

1.进程简介

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

  进程自己是不能执行的,必须通过线程来执行;


2.线程简介

  线程是操作系统能够进行运算调度的最小单位(操作系统调度CPU最小的单位就是线程)。它被包含在进程之中,是进程中的实际运作单位。

  线程是一个执行的上下文(指令),它是CPU执行指令流所需的全部信息。

  一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。


3.进程和线程的区别

  线程共享创建它的进程的地址空间;进程都有自己的地址空间(相同父进程的多个子进程也是这样的)。同一进程中的线程是共享数据,进程是不共享数据(相同父进程的多个子进程也是这样的)

  线程可以直接访问其进程的数据段;子进程拥有父进程数据段的副本。

  线程可以与同一进程的其他线程直接通信;进程必须使用进程间通信(IPC)来与同级进程通信。

  新的线程很容易创建;新的进程需要对父进程的复制(克隆)。

  线程可以对同一进程的其他线程进行相当大的控制和操作;进程只能对子进程进行控制和操作。

  对主线程的更改(取消、优先级更改等)可能影响同一进程的其他线程的行为;对父进程的更改不会影响子进程(只要不删除父进程就不会影响子进程)。



三、python调用线程的方法

1.直接调用线程的方式

import threading
import time


# 定义线程要运行的函数
# 函数名可以随意命名
def run(n):
    print("task", n)

    time.sleep(2)


if __name__ == "__main__":

    # group默认为空,为将来的扩展预留给ThreadGroup类实现的
    # target是由run方法函数调用的可调用对象。默认为空,代表着什么都不做
    # name是线程名称。默认情况下,构造一个唯一的名称:thread-n,n是一个十进制数
    # args是target调用的可调用对象的参数元组,默认为()。即使只有一个参数,也要加上逗号
    # kwargs是target调用的可调用对象的关键字参数字典
    t1 = threading.Thread(target=run, args=(1,))  # 生成一个线程实例
    t2 = threading.Thread(target=run, args=(2,))  # 生成另一个线程实例

    t1.start()  # 启动一个线程
    t2.start()  # 启动另一个线程


2.继承式调用线程(自定义线程类)的方式

import threading
import time


class MyThread(threading.Thread):
    """自定义的线程类"""
    def __init__(self, num):
        # 先重构构造函数,再继承父类的构造函数
        super(MyThread, self).__init__()
        self.num = num

    # 定义线程要运行的方法函数
    # 注意,方法函数名必须要是run,因为程序里已经写死,会自动调用run方法函数
    def run(self):
        print("运行的数字是:%s" % self.num)

        time.sleep(3)


if __name__ == '__main__':

    t1 = MyThread(1)  # 生成一个线程实例,并传递参数
    t2 = MyThread(2)  # 生成另一个线程实例,并传递参数

    t1.start()  # 启动一个线程
    t2.start()  # 启动另一个线程


3.线程的其它方法

  print(线程实例.getName()):获取线程名;


  线程实例.setName(name):为线程设置名称;


  线程实例.setDaemon():设置线程为后台线程(守护线程)或前台线程,默认情况下所有线程都是前台线程。线程实例名.setDaemon()代表将当前线程设置成前台线程,线程实例名.setDaemon(True)代表将当前线程设置成后台线程(守护线程)。


  线程实例.join(timeout):等待线程终止,timeout是等待的秒数,timeout为空就代表一直等到线程终止。等待的线程执行完毕后,主线程才继续往下执行,该方法使得多线程变得无意义;


  线程实例.run():线程被cpu调度后自动执行线程对象的run方法;


  print(threading.active_count()):查看当前活跃的线程数;


  print(threading.current_thread()):查看当前线程的实例,主线程叫MainThread,子线程叫Thread-n;


4.程序开启线程后的注意事项

  主程序是主线程在执行;

  主线程创建子线程后,主线程不会等待子线程执行完毕后再向下执行。也就是说主线程和子线程是并行的;



四、join - 等待线程执行

线程实例.join(timeout):等待线程终止,timeout是等待的秒数,timeout没有指定就代表一直等到线程终止。等待的线程执行完毕后,主线程才继续往下执行;

import threading
import time


def run(n):
    print("task", n)

    time.sleep(2)

    print("task done", n)

    print("查看当前线程", threading.current_thread())


if __name__ == "__main__":
    t_obj_list = []

    start_time = time.time()

    for i in range(3):  # 启动3个线程
        t = threading.Thread(target=run, args=("t-%s" % i,))  # 生成线程实例
        t.start()  # 启动一个线程
        t_obj_list.append(t)

    print("当前活跃的线程数", threading.active_count())

    for item in t_obj_list:
        item.join()  # 等待线程终止

    print("程序执行总耗时 %s 秒" % (time.time() - start_time))

    print("查看当前线程", threading.current_thread())



五、守护线程

1.什么是守护线程

  当主线程执行完毕,任何守护线程不管是否执行完成,都会自动终止。程序只会等待主线程、非守护线程执行完成后再退出程序;

  无法将主线程设置成守护线程;

  守护线程一直在监听主线程是否退出;



2.设置守护线程的方法

  线程实例.setDaemon():设置线程为后台线程(守护线程)或前台线程,默认情况下所有线程都是前台线程。线程实例名.setDaemon()代表将当前线程设置成前台线程,线程实例.setDaemon(True)代表将当前线程设置成后台线程(守护线程)。

    如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止。

    如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止

  注意,一定要在启动线程之前设置,不然就设置不了;


3.实例

import threading
import time


def run(n):
    print("task", n)

    time.sleep(2)

    print("task done", n)

    print("查看当前线程", threading.current_thread())


if __name__ == "__main__":

    for i in range(3):  # 启动3个线程
        t = threading.Thread(target=run, args=("t-%s" % i,))  # 生成线程实例
        t.setDaemon(True)  # 将当前线程设置成守护线程。一定要在启动线程之前设置
        t.start()  # 启动一个线程

    print("当前活跃的线程数", threading.active_count())

    print("查看当前线程", threading.current_thread())



六、GIL - 全局解释器锁

  全局解释器锁(GIL)的作用就是同一时间只让一个线程调用CPU;

  机器上无论有多少个CPU,通过CPython无论启动多少个线程,在执行的时候全局解释器锁(GIL)同一时刻只允许一个线程执行操作;

  由于线程之间是进行随机调度,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了全局解释器锁(GIL),同一时刻只允许一个线程执行操作。



七、互斥锁

1.为什么要有互斥锁

  一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据。假设现在有A、B两个线程,此时都要对number进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了number=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时将CPU运算的结果再赋值给number变量后,结果就都是99。这样计算出的结果就有问题,那怎么办呢?很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁,这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。


2.加互斥锁的例子

import threading


def operation():
    global number  # 在每个线程中都获取这个全局变量

    print('线程实例 %s 获取的number值为 %s ' % (threading.current_thread(), number))

    lock.acquire()  # 修改数据前加锁

    number -= 1  # 对此公共变量进行-1操作

    lock.release()  # 修改完成后释放锁


number = 10  # 设定一个共享变量

thread_list = []

lock = threading.Lock()  # 生成全局锁

for i in range(10):
    t = threading.Thread(target=operation)
    t.start()

    thread_list.append(t)

# 等待所有线程执行完毕,不然主线程就会往下执行,有可能某些线程还未执行完毕,主线程打印出的number值有误
for t in thread_list:
    t.join()

print('所有线程修改完成后number的值为', number)


3.递归锁

有时在修改数据时需要套多层锁(大锁中还包含有子锁),如果使用普通的锁锁住数据,会出现在修改完数据后无法释放锁,锁死的情况。对于这种情况就要使用递归锁;



4.加递归锁的例子

import threading


def run1():
    print("第一次抓取数据")

    lock.acquire()  # 修改数据前再加上一把小锁

    global num  # 在线程中都获取这个全局变量
    num += 1

    lock.release()  # 修改完成后释放小锁

    return num


def run2():
    print("第二次抓取数据")

    lock.acquire()  # 修改数据前再加上一把小锁

    global num2  # 在线程中都获取这个全局变量
    num2 += 1

    lock.release()  # 修改完成后释放小锁

    return num2


def run3():

    lock.acquire()  # 修改数据前加上一把大锁

    res = run1()

    print('--------run1函数和run2函数之间-----')

    res2 = run2()

    lock.release()  # 修改完成后释放大锁

    print("run1函数返回的值是", res)
    print("run2函数返回的值是", res2)


if __name__ == '__main__':

    num, num2 = 0, 0

    lock = threading.RLock()  # 生成递归锁

    for i in range(2):  # 开启两个线程
        t = threading.Thread(target=run3)  # 开启的线程先调用run3函数
        t.start()

while threading.active_count() != 1:  # 子线程还未执行完毕
    print("当前活跃的线程数", threading.active_count())
else:
    print('----所有线程执行完毕---')
    print("num的值是", num)
    print("num2的值是", num2)



八、信号量

1.信号量简介

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

  放行线程的原则:当允许的线程中某个线程先执行完成释放锁后,程序会立即再放行新的线程,将允许的线程数稳定在设置的数量,不会等到所有允许的线程都执行完成后再统一放行;

  注意,由于信号量(Semaphore)是同时允许一定数量的线程更改数据,如果这些允许的线程更改的是同一份数据,那么有可能更改后的结果出现错误。


2.信号量的例子

import threading
import time


def run():
    semaphore.acquire()  # 加锁

    time.sleep(10)

    print("线程 %s 在运行" % threading.current_thread().name)

    semaphore.release()  # 释放锁


if __name__ == '__main__':

    # 生成信号量实例,并设置最多允许3个线程可以同时修改数据
    semaphore = threading.BoundedSemaphore(3)

    for i in range(10):  # 开启10个线程
        t = threading.Thread(target=run)
        t.start()

while threading.active_count() != 1:
    pass
else:
    print('所有线程都执行完毕')



九、事件

1.事件简介

  python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。

  事件处理的机制:全局定义了一个标记,如果标记值为False,那么执行event.wait方法时线程就会阻塞,如果标记值为True,那么执行event.wait方法时线程不会阻塞。

  任何线程都可以设置标记、重置标记和等待相同的事件;


2.事件的例子

import threading
import time


event = threading.Event()


def traffic_lights():
    count = 1

    # 创建事件对象后标记默认为False,这里将标记设置为True,代表绿灯
    event.set()
    while True:
        if 5 < count <= 10:  # 红灯时间5秒钟
            event.clear()  # 将标记设置为False,代表红灯
            print("\033[41;1m红灯亮了\033[0m")
        elif count > 10:  # 绿灯时间5秒
            event.set()  # 将标记设置为True,代表绿灯
            count = 1  # 将count重置
            print("\033[42;1m绿灯亮了\033[0m")
        else:
            print("\033[42;1m绿灯亮了\033[0m")

        time.sleep(1)
        count += 1


def car(*args):
    while True:
        if event.is_set():  # 标记为True,代表绿灯
            print("%s通行" % args[0])
            print("%s通行" % args[1])
            time.sleep(1)
        else:
            print("停车等待")
            event.wait()  # 处理阻塞状态,等待设置标记


light_obj = threading.Thread(target=traffic_lights)
light_obj.start()

for i in range(2):  # 开启两个车的线程
    car_obj = threading.Thread(target=car, args=("Tesla", "Benz"))
    car_obj.start()



十、队列

1.队列简介

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

  队列用来进行线程间通讯,让各个线程之间共享数据。

  队列的两个作用:解耦和提高运行效率;


  队列和列表的区别:队列中的数据取走就没有了,而从列表中取数据是复制,只有手动删除数据,数据才会没有;


2.队列的模式

①先入先出模式

队列对象名 = queue.Queue(maxsize=0)


②后入先出模式

队列对象名 = queue.LifoQueue(maxsize=0)


③往队列中放入数据时设置优先级,按照优先级出队列(数小的优先,字母靠前的优先),格式为一个元组:(优先级, 数据)

队列对象名 = queue.PriorityQueue(maxsize=0)


注意,maxsize默认值为零,当maxsize的值小于等于零时,队列的大小是无限的。


3.队列的其它方法

队列对象.put(item, block=True, timeout=None):队列没有满,将项目放入队列中。队列已满时:

  ●block的值为True,timeout的值为None:方法函数会一直阻塞到队列中有空闲空间,并将项目放入队列中为止;

  ●block的值为True,timeout的值为一个正数:方法函数会一直阻塞到设置的超时时间为止,在超时时间内,如果队列中有空闲空间就将项目放入队列中,到了设置的超时时间就抛出queue.Full的异常。

  ●block的值为False:直接抛出queue.Full的异常(在这种情况下,超时时间将被忽略);


队列对象.put_nowait():队列没有满,将项目放入队列中。队列已满,就抛出queue.Full的异常。


print(队列对象.get(block=True, timeout=None)):队列中有项目,就立即从队列中移除并返回一个项目。队列中没有项目时:

  ●block的值为True,timeout的值为None:方法函数会一直阻塞到队列中有项目,并将项目从队列中移除并返回为止;

  ●block的值为True,timeout的值为一个正数:方法函数会一直阻塞到设置的超时时间为止,在超时时间内,如果队列中有项目时就移除并返回一个项目,到了设置的超时时间就抛出queue.Empty的异常;

  ●block的值为False:直接抛出queue.Empty的异常(在这种情况下,超时时间将被忽略);


print(队列对象.get_nowait()):队列中有项目,就立即从队列中移除并返回一个项目。队列中没有项目,就抛出queue.Empty的异常。


print(队列对象.qsize()):返回队列中项目的个数;


print(队列对象.empty()):如果队列为空返回True,否则返回False;


print(队列对象.full()):如果队列已满返回True,否则返回False;


4.队列的实例

import threading
import time
import queue


def producer():
    count = 1

    while True:
        # 队列没有满,将骨头放入队列中
        # 队列已满时,线程一直阻塞到队列中有空闲空间,并将骨头放入队列中为止
        q.put("骨头%s" % count)
        print("生产了骨头%s" % count)

        count += 1

        time.sleep(0.5)


def consumer(name):
    while True:
        # 队列中有骨头时,就立即从队列中移除并返回一个骨头
        # 队列中没有骨头时,线程一直阻塞到队列中有骨头,并将骨头从队列中移除并返回为止
        print("%s 取到 %s 并吃了它" % (name, q.get()))

        time.sleep(1)


q = queue.Queue(10)

p = threading.Thread(target=producer)
c1 = threading.Thread(target=consumer, args=("哈士奇",))
c2 = threading.Thread(target=consumer, args=("泰迪",))

p.start()
c1.start()
c2.start()