day9_线程

paramiko模块

paramiko模块 存在的作用:该模块基于SSH用于连接远程服务器并作批量管理用的。

SSHClient

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

1、基于用户名密码连接

1.1正常创建用户名密码连接的sshclient

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

import paramiko

ssh = paramiko.SSHClient()#创建ssh对象
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())#允许连接不在know_hosts文件中的主机
ssh.connect(hostname='192.168.224.133',port=22,username='liyanan',password='123')#连接服务器
stdin,stdout,stderr = ssh.exec_command('df -h')#执行命令
result = stdout.read()#获取命令结果
print(result.decode())
ssh.close()#关闭连接

注:
1、stdin => 标准输入,就是你输入的那个命令
2、stdout => 标准输出,你输入命令后执行的结果
3、stderr => 标准错误,名利执行的过程中,如果出错了,就把这个错误打到这里


1.2SSHClient 封装 Transport

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

import paramiko

transport = paramiko.Transport(('192.168.224.133',22))
transport.connect(username='liyanan',password='123')
ssh = paramiko.SSHClient()
ssh._transport = transport

stdin,stdout,stderr = ssh.exec_command('df')
res = stdout.read()
print(res.decode())
transport.close()
2、基于公钥密钥连接

2.1正常创建公钥密钥连接的sshclient

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

property_key = paramiko.RSAKey.from_private_key_file('/home/liyanan/.ssh/id_rsa')#私钥路径

ssh = paramiko.SSHClient()#创建ssh对象
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())#允许连接不在know_hosts文件中的主机
ssh.connect(hostname='192.168.224.133',port=22,username='liyanan',pkey=property_key)
stdin, stdout, stderr = ssh.exec_command('df')
# 获取命令结果
result = stdout.read()
print(result.decode())

# 关闭连接
ssh.close()

2.2SSHClient 封装 Transport

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

import paramiko
private_key = paramiko.RSAKey.from_private_key_file('/home/liyanan/.ssh/id_rsa')
transport = paramiko.Transport(('192.168.224.133',22))
transport.connect(username='liyanan',pkey=private_key)
ssh = paramiko.SSHClient()
ssh._transport = transport

stdin,stdout,stderr = ssh.exec_command('df')
res = stdout.read()
print(res.decode())
transport.close()

SFTPClient

作用:用于连接远程服务器并执行上传下载
1、基于用户名密码上传下载

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

transport = paramiko.Transport(('192.168.224.133',22))
transport.connect(username='liyanan',password='123')
sftp = paramiko.SFTPClient.from_transport(transport)
sftp.put('/tmp/a.py','/tmp/a1.py')
sftp.get('/tmp/b.py','/tmp/b1.py')

transport.close()

2、基于公钥密钥上传下载

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

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

transport = paramiko.Transport(('192.168.224.133',22))
transport.connect(username='liyanan',pkey=private_key)

sftp = paramiko.SFTPClient.from_transport(transport)

sftp.put('/tmp/a.py','/tmp/a1.py')
sftp.get('/tmp/b.py','/tmp/b1.py')

transport.close()

线程和进程介绍

进程:是以一个整体的形式暴露给操作系统管理,里面包含对各种资源的调用,内存的管理,网络接口的调用等等。。。。对各种资源管理的集合,就可以成为进程。
线程:是操作系统的最小的调度单位,是遗传指令的集合。
这里写图片描述


进程

概念:程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
有了进程为什么还要线程?
1、进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
2、进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
一个操作系统就像是一个工厂,工厂里面有很多个生产车间,不同的车间生产不同的产品,每个车间就相当于一个进程,且你的工厂又穷,供电不足,同一时间只能给一个车间供电,为了能让所有车间都能同时生产,你的工厂的电工只能给不同的车间分时供电,但是轮到你的qq车间时,发现只有一个干活的工人,结果生产效率极低,为了解决这个问题,应该怎么办呢?。。。。没错,你肯定想到了,就是多加几个工人,让几个人工人并行工作,这每个工人,就是线程!

线程

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

区别:

1、线程是共享内存空间的;进程的内存是独立的。
2、线程可以直接访问此进程中的数据部分;进程有他们独立拷贝自己父进程的数据部分,每个进程是独立的
3、同一进程的线程之间直接交流(直接交流涉及到数据共享,信息传递);两个进程想通信,必须通过一个中间代理来实现。
4、创建一个新的线程很容易;创建新的进程需要对其父进程进行一次克隆。
5、一个线程可以控制和操作同一进程里的其他线程;但是进程只能操作子进程。
6、对主线程的修改,可能会影响到进程中其他线程的修改;对于一个父进程的修改不会影响其他子进程(只要不删除父进程即可)

进程和线程那个运行快?

注意了,它俩是没有可比性的,线程是寄生在进程中的,你问它俩谁快。说白了,就是问在问两个线程谁快。因为进程只是资源的集合,进程也是要起一个线程的,它俩没有可比性。

进程和线程那个启动快?

答案是:线程快。因为进程相当于在修一个屋子。线程只是一下把一个来过来就行了。进程是一堆资源的集合。它要去内存里面申请空间,它要各种各样的东西去跟OS去申请。但是启动起来一运行,它俩是一样的,因为进程也是通过线程来运行的。

总结

1、线程是操作系统最小的调度单位,是一串指令的集合。
2、进程要操作CPU,必须先创建一个线程。
3、进程本身是不可以执行的,操作cpu是通过线程实现的,因为它是一堆执行,而进程是不具备执行概念的。就像一个屋子,屋子就是进程,但是屋子里面的每一个人就是线程,屋子就是内存空间。
4、单核CPU只能同时干一件事,但是为什么给我们的感觉是在干了很多件事?因为上线的切换,刚才也说了跟读书那个例子一样。因为CPU太快了,可以有N多次切换,其实它都是在排着队呐。
5、寄存器是存上下文关系的。
6、进程是通过PID来区分的,并不是通过进程名来区分的。进程里面的第一个线程就是主线程,父线程和子线程是相互独立的,只是父线程创建了子线程,父线程down了,子线程不会受到影响的。
7、主线程修改影响其他线程的行文,因为它们是共享数据的。


并发多线程

我们说单核的cpu只能同时执行一个任务,但是给我们的一个幻觉是可以执行多个,因为cpu太快了。它是怎么实现的呢?就是上下文切换,它不是轮询着切换的。它是按照优先级来切换的,并不是从头到尾的,中间的切换是有优先级的。我们就可以利用这个优势,因为它太快了,但是我们只打开了一个QQ,启动一个线程的话,他能得到执行的时间就是有限的。那cpu给我们的感觉同时执行多个任务,那我就可以并发批量操作某个动作。
1、创建线程
我们通过threading.Thread模块去创建线程

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

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

t1 = threading.Thread(target=run,args=('t1',))#args后面1个参数也要加逗号,要不然会把它当做一个参数,实际它是一个元组
t2 = threading.Thread(target=run,args=('t2',))

t1.start()
t2.start()

输出:

task t1
task t2
#t1和t2同时打印处理然后等待2秒,程序结束。

2、不用线程的情况

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

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

t1 = threading.Thread(target=run,args=('t1',))#args后面1个参数也要加逗号,要不然会把它当做一个参数,实际它是一个元组
t2 = threading.Thread(target=run,args=('t2',))

# t1.start()
# t2.start()

run('t1')
run('t2')

输出

task t1
task t2
t1显示出来等待2秒,然后t2显示,再等2秒,程序结束。

继承式多线程

之前,我们只是简单的介绍了多线程演示,也通过时间设置看出来了,多线程和单线程的不同。现在我们进行更深入的了解,来聊一聊,另外一种多线程方式,继承式多线程,和一个多线程的等待。
定义:继承式多线程是自己自定义类,去继承theading.Tread这个类,通过启动,去执行run方法中的代码。

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

class Mythread(threading.Thread):#继承threading.Therad
    def __init__(self,n):
        super(Mythread,self).__init__()
        self.n = n

    def run(self):#重写run方法,这个方法不能叫其他名字,只能叫run
        print('running on n:%s'%self.n)

        time.sleep(2)

t1 = Mythread('t1')#实例化
t2 = Mythread('t2')
t1.start()#启动一个多线程
t2.start()

启动多个线程

我们之前只启动了一个2个线程,还是用那种古老的方式t1,t2。那我想一下子起10个或者100个线程,该如何起呐?这边我们可以启动线程的时候,把它加到循环里面去。并且来计算一下它的时间。
我们这边为了方便实验,就启动5个线程吧,暂时不启动那么多,并且计算一下时间。那有些同学说了,你为啥不启动1000个线程或者更多一点的线程。这边注意了:你的计算机是4核的,它能干的事情,就是4个任务。你启动的线程越多,就代表着要在这个很多线程之间进行上下文切换。相当于我有一本书,我只看了半页,因为cpu要确保每个人都能执行。也就是说我一本说我要确保你们每一位同学都能看到,那就相当于每个人执行的时间非常少。我把这本说拿过来,一下子又被第二个人,第三个人拿走了。所以就导致所有的人都慢了,所以说你的线程启动1000,启动10000就没有意义了,导致机器越来越慢,所以要适当设置。下面我们就来看看一下子启动多个线程的例子,例子如下:

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

def run(n):#这边的run方法的名字是自行定义的,跟继承式多线程不一样,那个是强制的
    print('task',n)
    time.sleep(2)
    print('task done',n)

start_time = time.time()#开始时间

for i in range(5):
    t = threading.Thread(target=run,args=("t-{0}".format(i),))
    t.start()


print('-------all has finish')
print('cast:',time.time()-start_time)
#输出
task t-0
task t-1
task t-2
task t-3
-------all has finish
cast: 0.0020911693572998047
task t-4
task done t-0
task done t-1
task done t-2
task done t-3
task done t-4

从上面的程序发现问题,就是我主线程没有等其他的子线程执行完毕,就直接往下执行了,这是为什么呢?而且这个计算的时间根本不是我们想要的时间,中间的2秒哪里去了?
答案:一个程序至少有一个线程,那先往下走的,没有等的就是主线程,主线程启动了子线程之后,子线程就是独立的,跟主线程就没有关系了。主线程和它启动的子线程是并行关系,这就解释了为什么我的主线程启动子线程之后,没有等子线程,而继续往下走了。所以我们计算不出来时间,因为程序已经不是串行的了。程序本身就是一个线程,就是主线程。但是我就是想测试总共花了多长时间,咋办呐?

等待线程执行结果

1、join设置等待线程执行结果

通过设置在主线程里去等待子线程的执行结果,有了这个执行结果就完了,我要拿所有子线程的执行结果,如果有子线程的执行结果,我们一切都好办了。

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

class Mythread(threading.Thread):#继承threading.Therad
    def __init__(self,n):
        super(Mythread,self).__init__()
        self.n = n

    def run(self):#重写run方法,这个方法不能叫其他名字,只能叫run
        print('running on n:%s'%self.n)

        time.sleep(2)

t1 = Mythread('t1')#实例化
t2 = Mythread('t2')
t1.start()#启动一个多线程
t1.join()#等待t1线程的执行结果,相当于于其他语言里面的 t1.wait()
t2.start()

注:
1、t1.join() => 等待第一个线程的执行结果,这个结果在没有返回之前,程序是不往下走的。所以你这个这个程序什么吗?这个程序变成串行的了。
2、t2.start() => 这个后面没有写 join() 这个方法,但是程序在退出之前,它肯定要确保线程都执行完毕,所以它就默认就有一个join。所以最后不写。

2、实现并发效果

上面虽然有我想要的结果,却失去了并行的效果。我想要的是线程依然是并行效果,但是只不过,所有的线程统一之后再等主线程往下走。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
import 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('running on n:%s'%self.n)
        time.sleep(self.sleep_time)#没个线程可以传入不同的时间
        print('task done',self.n)

t1 = Mythread('t1',2)#t1传入2秒
t2 = Mythread('t2',4)

t1.start()
t2.start()

t1.join()#把t1.join()放在线程启动之后
print('main thead.')
#输出
running on n:t1
running on n:t2
task done t1
main thead.
task done t2

t1.join() => 这边只等t1的结果,然后主线程继续往下走,因为t2需要等4秒,所以,最后打出来的是t2的执行结果。t1的结果到了,就立刻算结果。这边只计算了t1的结果,没有t2的结果。如果想算t2的结果,就 t2.join()。上面代码不理解如图
这里写图片描述

计算多个线程的执行时间

我们用上面的执行重新改进一下第二知识点里面的代码,来计算一下10个线程启动执行的时间。

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

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

start_time = time.time()
t_obj = [] #存放子线程实例
for i in range(10):
    t = threading.Thread(target=run,args=('t-%s'%i,))

    t.start()
    t_obj.append(t)#为了不阻塞后面线程的启动,不在这里join,先放到一个列表中

for t in t_obj:
    t.join()#计算所有现成完成后的时间

print('-------all has finish',threading.current_thread())
print('cast:',time.time()-start_time)
#输出
task t-0
task t-1
task t-2
task t-3
task t-4
task t-5
task t-6
task t-7
task t-8
task t-9
task done t-0
task done t-1
task done t-2
task done t-3
task done t-4
task done t-5
task done t-6
task done t-7
task done t-8
task done t-9
-------all has finish <_MainThread(MainThread, started 139940125153024)>
cast: 2.006349802017212

查看当前线程和统计活动线程个数
用theading.current_thead()查看当前线程;用theading.active_count()来统计当前活动的线程数

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

def run(n):
    print('task',n)
    time.sleep(2)
    print('task done',n,threading.current_thread())#查看每个子线程

start_time = time.time()

for i in range(10):
    t = threading.Thread(target=run,args=('t-%s'%i,))
    t.start()


print('-------all has finish',threading.current_thread(),threading.active_count())#查看主线程和当前活动的所有线程数
print('cast:',time.time()-start_time)
#输出
task t-0
task t-1
task t-2
task t-3
task t-4
task t-5
task t-6
task t-7
task t-8
-------all has finish <_MainThread(MainThread, started 140048298227456)> 11
cast: 0.0035653114318847656
task t-9
task done t-0 <Thread(Thread-1, started 140048266757888)>
task done t-1 <Thread(Thread-2, started 140048258365184)>
task done t-2 <Thread(Thread-3, started 140048249972480)>
task done t-3 <Thread(Thread-4, started 140048241579776)>
task done t-4 <Thread(Thread-5, started 140048233187072)>
task done t-5 <Thread(Thread-6, started 140048224794368)>
task done t-6 <Thread(Thread-7, started 140047877666560)>
task done t-7 <Thread(Thread-8, started 140047869273856)>
task done t-8 <Thread(Thread-9, started 140047860881152)>
task done t-9 <Thread(Thread-10, started 140047852488448)>

线程个数=子线程数+主线程数


守护线程

我们说在不加join的时候,主线程和子线程完全是并行的,没有了依赖关系,你主线程执行了,我子线程也执行了。但是加了join之后,主线程依赖子线程执行完毕才往下走。现在我们要把所有的子线程编程我的守护进程。

守护进程:说白了,你是主人,你搞了几个仆人,这些个仆人都是为你服务的。可以帮你做很多事情,一个主人可以有多个守护进程,它们为你服务的前提是,主线程必须存在,如果主线程不存在,则守护进程也没了。那守护进程是干嘛的呢?帮你管理一些资源,打开一些文件,监听一些端口,监听一些资源,把一些垃圾资源会后,它可以干很多事情,这些完全定义,你想干嘛干嘛。

只要主线程执行完毕,它不管子线程有没有执行完毕。就退出了。所以我现在就可以把所有的子线程变成所有的守护线程。变成守护线程之后,主程序就不会等子线程结束载退出了。它会等待非守护线程执行完毕才退出。所以不用管这些守护线程,守护线程是仆人,不重要,主线程不管的。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading,time

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

start_time = time.time()
for i in range(5):
    t = threading.Thread(target=run,args=("t-{0}".format(i),))
    t.setDaemon(True)#Daemon意思是进程,这边是把当前线程设置为守护线程
    t.start()

print("--------all thead has finished")
print("cost:",time.time()-start_time)
#输出
task: t-0
task: t-1
task: t-2
task: t-3
task: t-4
--------all thead has finished
cost: 0.010882139205932617

守护进程一定要在start之前设置,start之后就不能设置了,之后设置会报错,所以必须之前设置。从上面可以看出,主线程是执行完毕,但是不会等守护线程执行完毕。
使用场景
比如你写一个socket_server,每一个链接过来,socket_server就会给这个链接分配一个新的线程。如果我手动的把socket_server停掉。那这种情况你必须手动停掉服务,那它就要down了,这种情况下还要等线程结束吗?就不用等线程结束了,它自己就直接结束了。这样,是不是就可以把每个socket线程设置一个守护线程。主线程一旦down掉,就全部退出。


线程锁(互斥锁Mutex)

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

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


def addNum():
    global num  # 在每个线程中都获取这个全局变量
    print('--get num:', num)
    time.sleep(1)
    num -= 1  # 对此公共变量进行-1操作


num = 100  # 设定一个共享变量
thread_list = []
for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

for t in thread_list:  # 等待所有线程执行完毕
    t.join()

print('final num:', num)

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

这里写图片描述
图解:
1、到第5步的时候,可能这个时候python正好切换了一次GIL(据说python2.7中,每100条指令会切换一次GIL),执行的时间到了,被要求释放GIL,这个时候thead 1的count=0并没有得到执行,而是挂起状态,count=0这个上下文关系被存到寄存器中.
2、然后到第6步,这个时候thead 2开始执行,然后就变成了count = 1,返回给count,这个时候count=1.
3、然后再回到thead 1,这个时候由于上下文关系,thead 1拿到的寄存器中的count = 0,经过计算,得到count = 1,经过第13步的操作就覆盖了原来的count = 1的值,所以这个时候count依然是count = 1,所以这个数据并没有保护起来.
加锁版本:

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

import time
import threading


def addNum():
    global num  # 在每个线程中都获取这个全局变量
    print('--get num:', num)
    time.sleep(1)
    lock.acquire()  # 修改数据前加锁
    num -= 1  # 对此公共变量进行-1操作
    lock.release()  # 修改后释放


num = 100  # 设定一个共享变量
thread_list = []
lock = threading.Lock()  # 生成全局锁
for i in range(100):
    t = threading.Thread(target=addNum)
    t.start()
    thread_list.append(t)

for t in thread_list:  # 等待所有线程执行完毕
    t.join()

print('final num:', num)

小结:
1、用theading.Lock()创建一个lock的实例。
2、在线程启动之前通过lock.acquire()加加锁,在线程结束之后通过lock.release()释放锁。
3、这层锁是用户开的锁,就是我们用户程序的锁。跟我们这个GIL没有关系,但是它把这个数据相当于copy了两份,所以在这里加锁,以确保同一时间只有一个线程,真真正正的修改这个数据,所以这里的锁跟GIL没有关系,你理解就是自己的锁。
4、加锁,说明此时我来去修改这个数据,其他人都不能动。然后修改完了,要把这把锁释放。这样的话就把程序编程串行了。
使用场景:
1、我们在程序中间不能有sleep,因为程序变成串行,这样你再sleep,程序执行的时间就会变长。
2、我们使用的时候确保数据量不是特别大,如果数据量大,也会影响我们的执行效率。
3、如果你程序结束时,不释放锁的话,而且程序又是串行的,则就是占着坑,那永远在那边等着,所以最后需要释放锁。


全局解释器锁(GIL)

我的机器有4核,代表着同一时间,可以干4个任务。如果单核cpu的话,我启动10个线程,我看上去也是并发的,因为是执行了上下文的切换,让我看上去是并发的。但是单核永远肯定时串行的,它肯定是串行的,cpu真正执行的时候,因为一会执行1,一会执行2.。。。。正常的线程就是这个样子的。
但是,在python中,无论你有多少核,永远都是假象。无论你是4核,8核,还是16核…….不好意思,同一时间执行的线程只有一个(线程),它就是这个样子的。这个是python的一个开发时候,设计的一个缺陷,所以说python中的线程是假线程。
GIL存在的意义
因为python的线程是调用操作系统的原生线程,这个原生线程就是C语言写的原生线程。因为python是用C写的,启动的时候就是调用的C语言的接口。因为启动的C语言的远程线程,那它要调这个线程去执行任务就必须知道上下文,所以python要去调C语言的接口的线程,必须要把这个上限问关系传给python,那就变成了一个我在加减的时候要让程序串行才能一次计算。就是先让线程1,再让线程2…….
每个线程在执行的过程中,python解释器是控制不了的,因为是调的C语言的接口,超出了python的控制范围,python的控制范围是只在python解释器这一层,所以python控制不了C接口,它只能等结果。所以它不能控制让哪个线程先执行,因为是一块调用的,只要一执行,就是等结果,这个时候4个线程独自执行,所以结果就不一定正确了。有了GIL,就可以在同一时间只有一个线程能够工作。虽然这4个线程都启动了,但是同一时间我只能让一个线程拿到这个数据。其他的几个都干等。python启动的4个线程确确实实落到了这4个cpu上,但是为了避免出错。这也是Cpython的一个缺陷,其他语言没有,仅仅只是Cpython有
GIL(全局解释器锁)是加在python解释器里面的,效果如图:
这里写图片描述
为什么GIL锁要加在python解释器这一层,而却不加在其他地方?
因为你python调用的所有线程都是原生线程。原生线程是通过C语言提供原生接口,相当于C语言的一个函数。你一调它,你就控制不了了它了,就必须等它给你返回结果。只要已通过python虚拟机,再往下就不受python控制了,就是C语言自己控制了。你加在python虚拟机以下,你是加不上去的。同一时间,只有一个线程穿过这个锁去真正执行。其他的线程,只能在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。


递归锁(RLock)

比如你进学校去班级,结果进学校的时候有一道门,进班级的时候又有一道门,你首先进入学校要把第一道门打开,然后锁上。进入学校之后,你又要进班级的门,然后锁上。然后你出班级,就是要打开第二把锁,然后再出校门打开第一把锁。但是这边有一个疑问,就是你在开锁的时候,两把锁的钥匙是一样的,也就是你被困在里面了,出不来了。多重锁把你完美的锁在里面了。代码如下:

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

import threading


def run1():
    print("grab the first part data")
    lock.acquire()  # 修改num前加锁
    global num
    num += 1
    lock.release()  # 释放锁
    return num


def run2():
    print("grab the second part data")
    lock.acquire()  # 修改num2前加锁
    global num2
    num2 += 1
    lock.release()  # 释放锁
    return num2


def run3():
    lock.acquire()  # 加锁
    res = run1()  # 执行run1函数
    print('--------between run1 and run2-----')
    res2 = run2()  # 执行run2函数
    lock.release()  # 释放锁
    print(res, res2)


if __name__ == '__main__':

    num, num2 = 0, 0
    lock = threading.Lock()  # 设置锁的全局变量
    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)

上面的执行结果,是无限的进入死循环,所以不能这么加。那咋办呢?下面我们就来说说递归锁。


递归锁的意思就是一把大锁还要包含子锁。用theading.RLock()设置递归锁。

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

import threading


def run1():
    print("grab the first part data")
    lock.acquire()  # 修改num前加锁
    global num
    num += 1
    lock.release()  # 释放锁
    return num


def run2():
    print("grab the second part data")
    lock.acquire()  # 修改num2前加锁
    global num2
    num2 += 1
    lock.release()  # 释放锁
    return num2


def run3():
    lock.acquire()  # 加锁
    res = run1()  # 执行run1函数
    print('--------between run1 and run2-----')
    res2 = run2()  # 执行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)

原理:
原理其实很简单的:就是递归锁,每开一把门,在字典里面存一份数据,退出的时候去到door1或者door2里面找到这个钥匙退出就OK了。

lock = {
door1:key1,
door2:key2
}

这里写图片描述
递归锁用于多重锁的情况,如果只是一层锁,我们不用。在实际情况下,递归锁场景用的不是特别多,所以知道就行了。


信号量

(线程锁)互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。
信号量:就是在同一时间,可以只允许设定的数执行,所以设置了信号量,就有多把锁。
用法:这个好比有3个人上厕所,就只有3把锁,如果有人进去,就会告诉你已经有人了,已经被占领了,其他的线程只能等。我们用threading.BoundedSemaphore(线程数)来定义一个信号量,代码如下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading,time
def run(n):
    semaphore.acquire()#增加信号量锁
    time.sleep(1)
    print('run the thread:%s\n'%n)
    semaphore.release()#释放锁

if __name__ == '__main__':

    semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时运行(Bounded:绑定,Semaphore:信号量)
    for i in range(20):
        t = threading.Thread(target=run,args=(i,))
        t.start()

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

上面程序的执行,给我们的感觉是:分了5组,这5个同时完成,又同时进5个进去。但是实际的效果是:这5个里面如果有3个完成,就会立刻再放3个进去。不会等5个都完成,每出来1个就放进去1个,出来几个放进去几个。
使用场景:
1、连接池,线程池,MySQL的有连接池,同一时间有个并发,能连多少个连接。
2、我们为了保证我的socket_server,因为python不会默认现在你启动多少个线程,但是你启动的线程越多,就会把系统拉的越慢,就会把程序拉的越慢。这里就可以搞一个我同一时间放100线程个进来,就是用semaphore
3、最后注意的是:python3.x 虽然不加锁也是正确的,但是最好还是把锁加上。


事件(event)

我们日常生活中经常遇到红绿灯,我们就很好理解红绿灯的例子,就是红灯停,绿灯行。
我现在生成一个线程,这个线程我让它扮演红绿灯,它每过一段时间就变成绿灯,又过一段时间变成红灯,又变成黄灯。然后我再生成3-5个线程作为车。车看见红灯,它就停下来等着,如果说是绿灯,车子就走。所以就涉及到红灯这个线程,红绿灯的这个线程就跟车线程之前产生了依赖了。就是红绿灯这个线程必须在绿灯的时候才能走,在红灯的时候就立刻停下来。所以互相之前,一个线程会根据另外一个线程的状态产生一些变化。类似这种场景的实现,就讲到了一个知识点:event,即事件。
就是我们红绿灯这个线程每隔30秒,由绿灯变成红灯,然后红灯会持续20秒,也就是没一次的状态切换,就是一次事件的发生。然后它切换了一次灯的状态。其他的车就会根据这个状态做不同的动作,这个就是因为一个事件导致其他事件的连锁变化。

例子1:
定义一个红绿灯的函数 ->定义车子的函数->启动两个线程

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading,time

event = threading.Event()#设置事件的全局变量
def ligther():
    count = 0
    event.set()#先设置绿灯
    while True:
        if count>5 and count<10:#改成红灯
            event.clear()#清除标志位
            print('\033[41mred light is on ....\033[0m')
        elif count>10:
            event.set()#创建标志位,变成绿灯
            count = 0
        else:
            print('\033[42mgree light is on ....\033[0m')

        time.sleep(1)
        count+=1

def car(name):
    while True:
        if event.is_set():#有标志位,代表是绿灯
            print('{0} running ....'.format(name))
            time.sleep(1)
        else:#代表红灯
            print('{0} sees red light ,waiting ....'.format(name))
            event.wait()#阻塞
            print('\033[32mgreen light is on , start going ...\033[0m')

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

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

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading,time
import random

def light():
    if not event.isSet():
        event.set()#wait不阻塞,绿灯状态
    count = 0
    while True:
        if count<10:
            print('\033[42;1m--green light on---\033[0m')
        elif count < 13:
            print('\033[43;1m--yellow light on---\033[0m')
        elif count < 20:
            if event.isSet():
                event.clear()
            print('\033[41;1m--red light on---\033[0m')
        else:
            count = 0
            event.set()#打开绿灯
        time.sleep(1)
        count+=1
def car(n):
    while 1:
        time.sleep(random.randrange(10))
        if event.isSet():#绿灯
            print("car [%s] is running.." % n)
        else:
            print("car [%s] is waiting for the red light.." %n)

if __name__ == '__main__':
    event = threading.Event()
    Light = threading.Thread(target=light)
    Light.start()
    for i in range(3):
        t=threading.Thread(target=car,args = (i,))
        t.start()

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

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import threading
import time
import random

def door():
    door_open_time_counter = 0
    while True:
        if door_swiping_event.is_set():
            print("\033[32;1mdoor opening....\033[0m")
            door_open_time_counter +=1

        else:
            print("\033[31;1mdoor closed...., swipe to open.\033[0m")
            door_open_time_counter = 0 #清空计时器
            door_swiping_event.wait()


        if door_open_time_counter > 3:#门开了已经3s了,该关了
            door_swiping_event.clear()

        time.sleep(0.5)


def staff(n):

    print("staff [%s] is comming..." % n )
    while True:
        if door_swiping_event.is_set():
            print("\033[34;1mdoor is opened, passing.....\033[0m")
            break
        else:
            print("staff [%s] sees door got closed, swipping the card....." % n)
            print(door_swiping_event.set())
            door_swiping_event.set()
            print("after set ",door_swiping_event.set())
        time.sleep(0.5)
door_swiping_event  = threading.Event() #设置事件


door_thread = threading.Thread(target=door)
door_thread.start()



for i in range(5):
    p = threading.Thread(target=staff,args=(i,))
    time.sleep(random.randrange(3))
    p.start()

队列

在多个线程之间安全的交换数据信息,队列在多线程编程中特别有用。
队列的类型:
1、class queue.Queue(maxsize=0) #先进先出,后进后出
2、class queue.LifoQueue(maxsize=0) # last in fisrt out,后进先出
3、class queue.PriorityQueue(maxsize=0) # 存储数据式,可设置优先级别的队列
为什么需要队列?
1、提高双方的效率,你只需要把数据放到队列中,中间去干别的事情。
2、完成了程序的解耦性,两者关系依赖性没有不大。


队列类别

1、class queue.Queue(maxsize=0)
先进先出,后进后出

>>> import queue
>>> q= queue.Queue()
>>> q.put(1)
>>> q.put(2)
>>> q.get()
1
>>> q.get()
2
>>> 

2、class queue.LifoQueue(maxsize=0)
先进后出,后进新出,last in fisrt out

>>> import queue
>>> q= queue.LifoQueue()
>>> q.put(1)
>>> q.put(2)
>>> q.get()
2
>>> q.get()
1
>>> 

3、class queue.PriorityQueue(maxsize=0)
根据优先级来取数据。存放数据的格式 : Queue.put((priority_number,data)),priority_number越小,优先级越高。

>>> import queue
>>> q= queue.PriorityQueue()
>>> q.put((1,'d1'))
>>> q.put((-1,'d2'))
>>> q.put((6,'d3'))
>>> q.get()
(-1, 'd2')
>>> q.get()
(1, 'd1')
>>> q.get()
(6, 'd3')

队列方法

1、exception queue.Empty
当队列中的数据为空时,就会抛出这个异常。

>>> import queue
>>> q= queue.Queue()
>>> q.get(block=False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python/lib/python3.6/queue.py", line 161, in get
    raise Empty
queue.Empty

2、exception queue.Full
当队列中满了以后,再放数据的话,就会抛出此异常。

>>> import queue
>>> q = queue.Queue(maxsize = 1)
>>> q.put(1)
>>> q.put(1,block = False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python/lib/python3.6/queue.py", line 130, in put
    raise Full
queue.Full

3、Queue.qsize()
查看队列的大小

>>> q  = queue.Queue()
>>> q.put(1)
>>> q.qsize()
1

4、Queue.empty()
队列如果为空返回True,不为空返回False

>>> q = queue.Queue()
>>> q.put(1)
>>> q.empty()
False
>>> q.get()
1
>>> q.empty()
True

5、Queue.full()
队列如果满了,返回True,没有满返回False

>>> q = queue.Queue(maxsize = 1)
>>> q.full()
False
>>> q.put(1)
>>> q.full()
True
>>> 

6、Queue.put(item,block=True,timeout=None)
把数据插入队列中。block参数默认为true,timeout默认值是None。如果blcok为false的话,那么在put时候超过设定的maxsize的值,就会报full 异常。如果timeout设置值得话,说明put值得个数超过maxsize值,那么会在timeout几秒之后抛出full异常。

>>> q = queue.Queue(maxsize = 1)
>>> q.put(1)
>>> q.put(1,block = False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python/lib/python3.6/queue.py", line 130, in put
    raise Full
queue.Full
>>> q.put(1,timeout =1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python/lib/python3.6/queue.py", line 141, in put
    raise Full
queue.Full
>>> 

7、Queue.put_nowait(item)
这个其实等同于Queue.put(item,block=False)或者是Queue.put(item,False)

>>> q=queue.Queue(maxsize=1)
>>> q.put(1)
>>> q.put_nowait(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python/lib/python3.6/queue.py", line 184, in put_nowait
    return self.put(item, block=False)
  File "/usr/local/python/lib/python3.6/queue.py", line 130, in put
    raise Full
queue.Full

8、Queue.get(block=True,timeout=None)
移除并返回队列中的序列。参数block=true并且timeout=None。如果block=false的话,那么队列为空的情况下,就直接Empty异常。如果timeout有实际的值,这个时候队列为空,执行get的时候,则时隔多长时间则报出Empty的异常。

>>> q= queue.Queue()
>>> q.put(1)
>>> q.get()
1
>>> q.get(block = False)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python/lib/python3.6/queue.py", line 161, in get
    raise Empty
queue.Empty
>>> q.get(timeout=1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python/lib/python3.6/queue.py", line 172, in get
    raise Empty
queue.Empty
>>> 

9、Queue.get_nowait(item)
其实这个等同于Queue.get(block=False)或者Queue.get(False)

>>> q= queue.Queue()
>>> q.put(1)
>>> q.get()
1
>>> q.get_nowait()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/local/python/lib/python3.6/queue.py", line 192, in get_nowait
    return self.get(block=False)
  File "/usr/local/python/lib/python3.6/queue.py", line 161, in get
    raise Empty
queue.Empty

10、Queue.task_done()
这个函数告诉队列消费者每消费一条信息,则要告诉消费者你已经消费了一个包子。
11、Queue.join()
block直到queue被消费完毕,如果生产者生产10个包子,那么要等消费者把这个10个包子全部消费完毕,生产者才能继续往下执行。


生产者消费者模型

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
1、为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
2、什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
例子1:生成者消费者模型

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

def producer():
    for i in range(10):
        q.put('包子 %s'%i)

        print('开始等待所有的包子被取走')
        q.join()
        print('所有的包子都取完了')
def consumer(n):
    while q.qsize()>0:
        print('%s 取到'%n,q.get())
        q.task_done()#告知这个认为执行完了

q = queue.Queue()

p = threading.Thread(target=producer,)
p.start()

c1 = consumer('li')
#输出
开始等待所有的包子被取走
li 取到 包子 0
li 取到 包子 1
li 取到 包子 2
li 取到 包子 3
li 取到 包子 4
li 取到 包子 5
li 取到 包子 6
li 取到 包子 7
li 取到 包子 8
li 取到 包子 9
所有的包子都取完了

例子2:边生产边消费

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import  time,random

import queue,threading

q = queue.Queue()
def Producer(name):
    count = 0
    while count<20:
        time.sleep(random.randrange(3))
        q.put(count)
        print('Producer %s has produced %s baozi..' %(name, count))
        count+=1
def Consumer(name):
    count = 0
    while count<20:
        time.sleep(random.randrange(4))
        if not q.empty():
            data = q.get()
            print(data)
            print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data))

        else:
            print("-----no baozi anymore----")
        count+=1

p1 = threading.Thread(target=Producer,args=('A',))
c1 = threading.Thread(target=Consumer,args=('B',))
p1.start()
c1.start()
#输出
-----no baozi anymore----
-----no baozi anymore----
-----no baozi anymore----
Producer A has produced 0 baozi..
Producer A has produced 1 baozi..
0
Consumer B has eat 0 baozi...
Producer A has produced 2 baozi..
Producer A has produced 3 baozi..
Producer A has produced 4 baozi..
1
Consumer B has eat 1 baozi...
Producer A has produced 5 baozi..
Producer A has produced 6 baozi..
Producer A has produced 7 baozi..
Producer A has produced 8 baozi..
2
Consumer B has eat 2 baozi...
Producer A has produced 9 baozi..
3
Consumer B has eat 3 baozi...
4
Consumer B has eat 4 baozi...
5
Consumer B has eat 5 baozi...
Producer A has produced 10 baozi..
Producer A has produced 11 baozi..
Producer A has produced 12 baozi..
Producer A has produced 13 baozi..
6
Consumer B has eat 6 baozi...
Producer A has produced 14 baozi..
Producer A has produced 15 baozi..
Producer A has produced 16 baozi..
7
Consumer B has eat 7 baozi...
8
Consumer B has eat 8 baozi...
Producer A has produced 17 baozi..
9
Consumer B has eat 9 baozi...
Producer A has produced 18 baozi..
Producer A has produced 19 baozi..
10
Consumer B has eat 10 baozi...
11
Consumer B has eat 11 baozi...
12
Consumer B has eat 12 baozi...
13
Consumer B has eat 13 baozi...
14
Consumer B has eat 14 baozi...
15
Consumer B has eat 15 baozi...
16
Consumer B has eat 16 baozi...

图解:

这里写图片描述
1、生产者生产,消费者消费。
2、消费者每消费一次,都要去执行以下task_done()方法,来告诉消费者你已经消费成功,相当于吃完饭,你应该给钱了。
3、消费者每消费一次,则队列中计数器会做减1操作。
4、当队列中的计数器为0的时候,则生产者不阻塞,继续执行,不为0的时候,则阻塞,直到消费者消费完毕为止。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值