python-线程、进程、协程

进程 && 线程
进程:是内存中的一个独立的句柄,我们可以理解为一个应用程序在内存中就是一个进程。 各个进程之间是内存相互独立,不可共享的
线程:每个应用运行之后就会对应启动一个主线程,通过主线程可以创建多个字线程,各个线程共享主进程的内存空间。
关于线程、进程的解释有一篇有趣而生动的解释(http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html)

默认应用程序:是单进程、单线程的。

  进程是资源分配的最小单位。与程序相比,程序只是一组指令的有序集合,它本身没有任何运行的含义,只是一个静态实体。进程是程序在某个数据集上 的执行,是一个动态实体。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消,反映了一个程序在一定的数据集上运行的 全部动态过程。每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。

  线程是轻量级的进程或子进程,是CPU调度的最小单位,所有的线程都存在于相同的进程。所以线程基本上是轻量级的进程,它负责在单个程序里执行 多任务。通常由操作系统负责多个线程的调度和执行。多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。

 

GIL(全局解释器锁)
我们知道多进程(mutilprocess) 和 多线程(threading)的目的是用来被多颗CPU进行访问, 提高程序的执行效率。 但是在python内部存在一种机制(GIL),在多线程 时同一时刻只允许一个线程来访问CPU。
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支持多线程,但是由于GIL的限制,在实际运行时,程序运行后开启多个线程,但在通过GIL后同时也只能有一个线程被CPU执行。

x进程和线程对比

 

 对比维度

多进程

多线程

总结

数据共享、同步

数据共享复杂,需要用IPC;数据是分开的,同步简单

因为共享进程数据,数据共享简单,但也是因为这个原因导致同步复杂

各有优势

内存、CPU

占用内存多,切换复杂,CPU利用率低

占用内存少,切换简单,CPU利用率高

线程占优

创建销毁、切换

创建销毁、切换复杂,速度慢

创建销毁、切换简单,速度很快

线程占优

编程、调试

编程简单,调试简单

编程复杂,调试复杂

进程占优

可靠性

进程间不会互相影响

一个线程挂掉将导致整个进程挂掉

进程占优

分布式

适应于多核、多机分布式;如果一台机器不够,扩展到多台机器比较简单

适应于多核分布式

进程占优

 

 

如何选用?

单进程,多线程的程序(io操作不占用CPU):如果是CPU密集型,那么则不能提高效率。如果是IO密集型,那么则能提高效率。

  多进程,单线程的程序:CPU密集型的,一般用多进程提高并发效率。

  小结:

  CPU密集型:多进程

  IO密集型:多线程

 

线程的创建方法:

有两种方式来创建线程:

一种是创建一个threading.Thread对象,在它的初始化函数(__init__)中将可调用对象作为参数传入。

一种是通过继承Thread类,重写它的run方法;

第一种:

import threading
def f1(arg):
    print(arg)
t = threading.Thread(target=f1,args=(123,))
t.start()
#什么时候去执行f1方法? 是在执行t.run()方法时执行f1()方法。t.run()不用我们去执行,代码自动执行

从上边可以看出,创建一个多线程程序,只需要3步:

  1. 创建执行函数

  2. 创建threading.Thread对象

        

def __init__(self, group=None, target=None, name=None,
                 args=(), kwargs=None, *, daemon=None):
        """This constructor should always be called with keyword arguments. Arguments are:

        *group* should be None; reserved for future extension when a ThreadGroup # 为了将来拓展保留的
        class is implemented.

        *target* is the callable object to be invoked by the run()
        method. Defaults to None, meaning nothing is called. # 一个可调用程序,在线程启动后执行

        *name* is the thread name. By default, a unique name is constructed of
        the form "Thread-N" where N is a small decimal number. # 线程的名字,默认值为“Thread-N“,N是一个数字

        *args* is the argument tuple for the target invocation. Defaults to (). # 参数args表示调用target时的参数列表

        *kwargs* is a dictionary of keyword arguments for the target
        invocation. Defaults to {}. # 参数kwargs表示调用target时的关键字参数。

        If a subclass overrides the constructor, it must make sure to invoke
        the base class constructor (Thread.__init__()) before doing anything
        else to the thread.

        """

 

      3. 启动线程

 

第二种:

import threading
class MyThread(threading.Thread):
    def __init__(self,func,args):
        self.func = func
        self.args = args
        super(MyThread,self).__init__()  #执行父类的构造方法

    def run(self):
        self.func(self.args)

def f2(arg):
    print(arg)

obj = MyThread(f2,123)
obj.start()

多线程的执行方法:

import time
from threading import Thread

def do_thread(num):
    print('this is thread %s' %str(num))
    time.sleep(3)

for i in range(5):
    t = Thread(target=do_thread,args=(i,))
    t.start()

以上方法就开启了一个5个线程,target用来定义开启线程后要执行的方法,args为参数

 线程的其它方法:

1 setName(), getName()

 setName():   给线程设置一个名字
  getName():   获取线程的名称

import time
from threading import Thread
def do_thread(num):
    print('this is thread %s' %str(num))
    time.sleep(2)
for i in range(2):
    t = Thread(target=do_thread,args=(i,))
    t.start()
    t.setName('Mythread_{0}'.format(str(i)))
    print(t.getName())

out:

this is thread 0
Mythread_0
this is thread 1
Mythread_1

2 setDaemon()

setDaemon(True/False): 设置创建的子线程为前台线程或后台线程.设置为True则子线程为后台线程。线程默认为前台线程(不设置此方法)
前台线程: 当子线程创建完成后,主线程和子线程(前台线程)同时运行,如果主线程执行完成,而子线程还未完成则等待子线程执行完成以后整个程序才结束。
后台线程: 当子线程创建完成后,如果子线程还未结束,而主线程运行结束则不管子线程了,程序就结束。
此方法设置必须在 start() 方法前进行设置, 看代码:

import time
from threading import Thread
def do_thread(num):
    print('this is thread %s' %str(num))
    time.sleep(3)
    print('OK',str(num))

for i in range(2):
    t = Thread(target=do_thread,args=(i,))
    #不设置此方法默认为前台线程
    #t.setDaemon(True)
    t.setName('Mythread_{0}'.format(str(i)))
    t.start()
    print(t.getName())

out:

this is thread 0
Mythread_0
this is thread 1
Mythread_1
OK 1
OK 0

后台线程:

import time
from threading import Thread
def do_thread(num):
    print('this is thread %s' %str(num))
    time.sleep(3)
    #执行到此时主线程执行完成了,程序结束,下面的代码不会执行
    print('OK',str(num))

for i in range(2):
    t = Thread(target=do_thread,args=(i,))
    #设置线程为后台线程
    t.setDaemon(True)
    t.setName('Mythread_{0}'.format(str(i)))
    t.start()
    print(t.getName())

out:

this is thread 0
Mythread_0
this is thread 1
Mythread_1

 3 join()
join(timeout) : 多线程的 wait(),当主线程执行 子线程.join() 方法后,主线程将等待子线程执行完再接着执行。当加上timeout参数后,如果超过timeout时间不管子线程有没有执行完都将结束等待
看下面两个例子

 

import time
from threading import Thread

def do_thread(num):
    time.sleep(3)
    print("this is thread %s" % str(num))


for i in range(2):
    t = Thread(target=do_thread, args=(i,))
    t.setName("Mythread_{0}".format(str(i)))
    t.start()
    print("print in main thread: thread name:", t.getName())

out:

print in main thread: thread name: Mythread_0
print in main thread: thread name: Mythread_1
this is thread 0
this is thread 1

 上面无join方法时,主线程执行完print,等待子线程函数中的print执行完成,这个程序退出。 下面我们看看加上join方法后的效果

import time
from threading import Thread

def do_thread(num):
    time.sleep(3)
    print("this is thread %s" % str(num))


for i in range(2):
    t = Thread(target=do_thread, args=(i,))
    t.setName("Mythread_{0}".format(str(i)))
    t.start()
    t.join()
    print("print in main thread: thread name:", t.getName())

out:

this is thread 0
print in main thread: thread name: Mythread_0
this is thread 1
print in main thread: thread name: Mythread_1

当程序运行到join后,将等待子程序执行完成,然后才向下执行。这样真个程序就变成一个单线程的顺序执行了。多线程就没什么鸟用了。

join()与setDaemon()都是等待子线程结束,有什么区别呢:
当执行join()后主线程就停了,直到子线程完成后才开始接着主线程执行,整个程序是线性的
setDaemon() 为前台线程时,所有的线程都在同时运行,主线程也在运行。只不过是主线程运行完以后等待所有子线程结束。这个还是一个并行的执行,执行效率肯定要高于join()方法的。

线程锁

线程锁分类:互斥锁,信号量,条件变量,事件

线程是内存共享的,当多个线程对内存中的同一个公共变量进行操作时,会导致线程争抢的问题,为了解决此问题,可以使用线程锁。

假设这样一种情况,有一个全局的计数num,每个线程获取这个全局的计数,根据num进行一些处理,然后将num加1

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

import threading
import time

NUM = 0
class MyThread(threading.Thread):
    def run(self):
        global NUM
        NUM += 1
        time.sleep(0.5)
        msg = self.name + ' set num to ' + str(NUM)
        print(msg)
if __name__ == '__main__':
    for i in range(5):
        t = MyThread()
        t.start()

out:

Thread-4 set num to 5
Thread-1 set num to 5
Thread-5 set num to 5
Thread-2 set num to 5
Thread-3 set num to 5

      问题产生的原因就是没有控制多个线程对同一资源的访问,对数据造成破坏,使得线程运行的结果不可预期。这种现象称为“线程不安全”。因此有了线程锁的概念。

  线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。 互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将 资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

我们现在引入锁,上面的代码修改为:

互斥锁:

 

import threading
import time

NUM = 0
class MyThread(threading.Thread):
    def run(self,l):
        #加锁
        l.acquire()
        global NUM
        NUM += 1
        time.sleep(0.5)
        msg = self.name + ' set num to ' + str(NUM)
        #解锁
        l.release()
        print(msg)
if __name__ == '__main__':
    lock = threading.Lock()
    for i in range(5):
        t = MyThread()
        t.run(lock)

 

out:

Thread-1 set num to 1
Thread-2 set num to 2
Thread-3 set num to 3
Thread-4 set num to 4
Thread-5 set num to 5

可以看出,加了线程锁之后,尝试任何次数的执行,最终结果都是一样的,消除了线程不安全的隐患。过程是这样的:我们先建立了一个 threading.Lock类对象lock,在run方法里,我们使用lock.acquire()获得了这个锁。此时,其他的线程就无法再获得该锁 了,他们就会阻塞在“l.acquire()”这里,直到锁被另一个线程释放l.release()。

上边的锁机制,叫做“互斥锁”。互斥锁的特点:同一时间仅允许一个线程进行更改数据。如果同一时间有多个线程更改数据的话,我们可以使用 信号量 来解决这个问题

信号量:

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

Semaphore信号量管理一个内置的计数器:
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

import threading
import time

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

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

执行之后会发现,每个1秒,会有5个线程执行,有print的操作。

import threading
import time

def do():
    semaphro.acquire()
    print('this is {0} set the semaphore'.format(threading.current_thread().getName()))
    time.sleep(2)
    semaphro.release()
    print("\033[1;30mthi is {0} release the semaphore\033[0m".format(threading.current_thread().getName()))

if __name__ == '__main__':
    semaphro = threading.Semaphore(2)
    for i in range(10):
        t = threading.Thread(target=do)
        t.setName("Thread_{0}".format(str(i)))
        t.start()
    print('finished')

上例中,虽然创建了10个线程,但同时只有2个线程在运行,就是因为在线程中通过Semaphore设置了2个信号量。只有其中一个释放后另其它的线程再能开始执行

out:

this is Thread_0 set the semaphore
this is Thread_1 set the semaphore
finished
thi is Thread_0 release the semaphore
thi is Thread_1 release the semaphore
this is Thread_3 set the semaphore
this is Thread_2 set the semaphore
thi is Thread_3 release the semaphore
this is Thread_4 set the semaphore
thi is Thread_2 release the semaphore
this is Thread_5 set the semaphore
thi is Thread_4 release the semaphore
thi is Thread_5 release the semaphore
this is Thread_7 set the semaphore
this is Thread_6 set the semaphore
this is Thread_8 set the semaphore
thi is Thread_6 release the semaphore
thi is Thread_7 release the semaphore
this is Thread_9 set the semaphore
thi is Thread_8 release the semaphore
thi is Thread_9 release the semaphore

条件变量:

互斥锁是最简单的线程同步机制,Python提供的Condition对象提供了对复杂线程同步问题的支持。Condition被称为条件变量, 除了提供与Lock类似的acquire和release方法外,还提供了wait和notify方法。线程首先acquire一个条件变量,然后判断一 些条件。如果条件不满足则wait;如果条件满足,进行一些处理改变条件后,通过notify方法通知其他线程,其他处于wait状态的线程接到通知后会 重新判断条件。不断的重复这一过程,从而解决复杂的同步问题。

  可以认为Condition对象维护了一个锁(Lock/RLock)和一个waiting池。线程通过acquire获得Condition 对象,当调用wait方法时,线程会释放Condition内部的锁并进入blocked状态,同时在waiting池中记录这个线程。当调用 notify方法时,Condition对象会从waiting池中挑选一个线程,通知其调用acquire方法尝试取到锁。

  Condition对象的构造函数可以接受一个Lock/RLock对象作为参数,如果没有指定,则Condition对象会在内部自行创建一个RLock

除了notify方法外,Condition对象还提供了notifyAll方法,可以通知waiting池中的所有线程尝试acquire内部 锁。由于上述机制,处于waiting状态的线程只能通过notify方法唤醒,所以notifyAll的作用在于防止有线程永远处于沉默状态。

  假设有这样一种场景:有一群生产者(Producer)和一群消费者(Consumer)通过一个市场来交互产品。生产者的”策略“是如果市场 上剩余的产品少于1000个,那么就生产100个产品放到市场上;而消费者的”策略“是如果市场上剩余产品的数量多余100个,那么就消费3个产品。用 Condition解决生产者与消费者问题的代码如下:

  参数说明: con.acquire和con.wait   通常在加锁的时候配合使用,表示在这里等待     

con.acquire() # 这三个必须放一起,是固定用法,通常用于解锁的时候
con.notify(num) # 表示一次放多少个线程过去
con.release()

第一种方式:

import threading
import time

class Producer(threading.Thread):
    def run(self):
        global count
        while True:
            if con.acquire():
                if count > 1000:
                    con.wait()
                else:
                    count = count+100
                    msg = self.name+' produce 100, count=' + str(count)
                    print(msg)
                    con.notify()
                con.release()
                time.sleep(1)

class Consumer(threading.Thread):
    def run(self):
        global count
        while True:
            if con.acquire():
                if count < 100:
                    con.wait()
                else:
                    count = count-3
                    msg = self.name+' consume 3, count='+str(count)
                    print(msg)
                    con.notify()
                con.release()
                time.sleep(1)

count = 500
con = threading.Condition()

def test():
    for i in range(2):
        p = Producer()
        p.start()
    for i in range(5):
        c = Consumer()
        c.start()
if __name__ == '__main__':
    test()


out:
Thread-1 produce 100, count=600
Thread-2 produce 100, count=700
Thread-3 consume 3, count=697
Thread-4 consume 3, count=694
Thread-5 consume 3, count=691
Thread-6 consume 3, count=688
Thread-7 consume 3, count=685
Thread-1 produce 100, count=785
Thread-3 consume 3, count=782
Thread-2 produce 100, count=882
Thread-4 consume 3, count=879
Thread-5 consume 3, count=876
Thread-6 consume 3, count=873
Thread-7 consume 3, count=870
Thread-4 consume 3, count=867
Thread-1 produce 100, count=967
Thread-5 consume 3, count=964
Thread-3 consume 3, count=961
Thread-2 produce 100, count=1061
Thread-6 consume 3, count=1058
Thread-7 consume 3, count=1055
Thread-3 consume 3, count=1052
Thread-4 consume 3, count=1049
Thread-5 consume 3, count=1046
Thread-7 consume 3, count=1043
Thread-6 consume 3, count=1040
Thread-3 consume 3, count=1037
Thread-4 consume 3, count=1034
Thread-5 consume 3, count=1031
Thread-7 consume 3, count=1028
Thread-6 consume 3, count=1025
Thread-3 consume 3, count=1022
Thread-4 consume 3, count=1019
Thread-5 consume 3, count=1016
Thread-7 consume 3, count=1013
condition解决同步问题案例代码

第二种方式:con.wait_for() 表示等待某个条件成立,如果成立的话继续,不成立的话等待,接收True或者False

con.acquire() # 这三个同样一起使用
con.wait_for(条件) # 等待条件返回True
con.release()
import threading

def condition():
    ret = False
    r = input(">>> ")
    if r == 'true':
        ret = True
    else:
        pass
    return ret


def func(i, con):
    print(i)
    con.acquire()  # con.acquire和con.wait 必须配合使用,表示在这里等待
    con.wait_for(condition)
    print(i+100)
    con.release()

c = threading.Condition()
for i in range(10):
    t = threading.Thread(target=func, args=(i, c,))
    t.start()
condition方式2

 小结:目前,可以解决线程间“同步问题”的方法,已经有2种:互斥锁和条件变量。其实队列也是一种!!!

队列解决同步问题代码案例

import threading
import time
from queue import Queue

class Producer(threading.Thread):
    def run(self):
        global queue
        count = 0
        while True:
            for i in range(100):
                if queue.qsize() > 1000:
                     pass
                else:
                     count = count +1
                     msg = '生成产品'+str(count)
                     queue.put(msg)
                     print(msg)
            time.sleep(1)

class Consumer(threading.Thread):
    def run(self):
        global queue
        while True:
            for i in range(3):
                if queue.qsize() < 100:
                    pass
                else:
                    msg = self.name + '消费了 '+queue.get()
                    print(msg)
            time.sleep(1)

queue = Queue()


def test():
    for i in range(500):
        queue.put('初始产品'+str(i))
    for i in range(2):
        p = Producer()
        p.start()
    for i in range(5):
        c = Consumer()
        c.start()
if __name__ == '__main__':
    test()
队列解决同步问题代码案例

 

那么除了条件,还有其他方法来解决生产者与消费者模型中的“同步问题”么?事件(event)

事件(event) 

很多时候,线程之间会有互相通信的需要。常见的情形是次要线程为主要线程执行特定的任务,在执行过程中需要不断报告执行的进度情况。前面的条件变量同步已经涉及到了线程间的通信(threading.Condition的notify方法)。更通用的方式是使用threading.Event对象。注意:事件其实也是一种锁,只不过这把锁是把线程全部锁住,直到信号被set为True的时候,线程才恢复运行。

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

  threading.Event可以使一个线程等待其他线程的通知。其内置了一个标志,初始值为False。线程通过wait()方法进入等待 状态,直到另一个线程调用set()方法将内置标志设置为True时,Event通知所有等待状态的线程恢复运行。还可以通过isSet()方法查询 Envent对象内置状态的当前值。

 

线程的事件, 用于主线程控制子线程的执行。它的本质就是定义了一个全局的flag标识,并通过一些方法来获取、设置此标识。包括:
wait()方法:当flag标识为False时,wait()方法将阻塞,为True时,wait()不阻塞
set()方法:设置flag标识为True
clear()方法: 设置flag标识为False
初始化时flag标识为False(阻塞状态)
is_set()/isSet() : 判断当前flag标识是否为True

import threading

def do(event):
    print('start')
    #默认初始化状态为False,到这里就阻塞了
    event.wait()
    print('execute\n')
if __name__ == '__main__':
    event_obj = threading.Event()
    for i in range(10):
        t = threading.Thread(target=do,args=(event_obj,))
        t.start()
    inp = input('input>>>>:')
    if inp == 'true':
        #如果为True,则flag=True ,不阻塞,子进程继续运行
        event_obj.set()
    else:
        event_obj.clear()

模拟红绿灯

import threading

def func(i, e):
    print(i)
    e.wait() # 表示在这里检测信号。如果检测到为红灯,则停止。如果为绿灯,则放行
    print(i+100)

event = threading.Event()

for i in range(10):
    t = threading.Thread(target=func, args=(i,event))
    t.start()

event.clear() # 全部都停止,设置为红灯

inp = input(">>> ")
if inp == "1":
    event.set()  # 表示放行,设置为绿灯
View Code

 

补充:Timer

定时器,指定n秒后执行某操作

from threading import Timer
 
 
def hello():
    print("hello, world")
 
t = Timer(1, hello)
t.start()  # after 1 seconds, "hello, world" will be printed
View Code

 

 锁的介绍:

1、死锁:

如果多个线程要调用多个现象,而A线程调用A锁占用了A对象,B线程调用了B锁占用了B对象,A线程不能调用B对象,B线程不能调用A对象,于是一直等待。这就造成了线程“死锁”
   Threading模块中,也有一个类,RLock,称之为可重入锁。该锁对象内部维护着一个Lock和一个counter对象。counter对象记 录了acquire的次数,使得资源可以被多次require。最后,当所有RLock被release后,其他线程才能获取资源。在同一个线程 中,RLock.acquire可以被多次调用,利用该特性,可以解决部分死锁问题

 

class _RLock:
    """This class implements reentrant lock objects.

    A reentrant lock must be released by the thread that acquired it. Once a
    thread has acquired a reentrant lock, the same thread may acquire it
    again without blocking; the thread must release it once for each time it
    has acquired it.

    """

    def __init__(self):
        self._block = _allocate_lock()
        self._owner = None
        # 初始化一个计数器
        self._count = 0

    def __repr__(self):
        owner = self._owner
        try:
            owner = _active[owner].name
        except KeyError:
            pass
        return "<%s %s.%s object owner=%r count=%d at %s>" % (
            "locked" if self._block.locked() else "unlocked",
            self.__class__.__module__,
            self.__class__.__qualname__,
            owner,
            self._count,
            hex(id(self))
        )

    def acquire(self, blocking=True, timeout=-1):
        """Acquire a lock, blocking or non-blocking.

        When invoked without arguments: if this thread already owns the lock,
        increment the recursion level by one, and return immediately. Otherwise,
        if another thread owns the lock, block until the lock is unlocked. Once
        the lock is unlocked (not owned by any thread), then grab ownership, set
        the recursion level to one, and return. If more than one thread is
        blocked waiting until the lock is unlocked, only one at a time will be
        able to grab ownership of the lock. There is no return value in this
        case.

        When invoked with the blocking argument set to true, do the same thing
        as when called without arguments, and return true.

        When invoked with the blocking argument set to false, do not block. If a
        call without an argument would block, return false immediately;
        otherwise, do the same thing as when called without arguments, and
        return true.

        When invoked with the floating-point timeout argument set to a positive
        value, block for at most the number of seconds specified by timeout
        and as long as the lock cannot be acquired.  Return true if the lock has
        been acquired, false if the timeout has elapsed.

        """
        me = get_ident()
        if self._owner == me:
            # 每次调用acquire,计数器加1
            self._count += 1
            return 1
        rc = self._block.acquire(blocking, timeout)
        if rc:
            self._owner = me
            self._count = 1
        return rc

    __enter__ = acquire

    def release(self):
        """Release a lock, decrementing the recursion level.

        If after the decrement it is zero, reset the lock to unlocked (not owned
        by any thread), and if any other threads are blocked waiting for the
        lock to become unlocked, allow exactly one of them to proceed. If after
        the decrement the recursion level is still nonzero, the lock remains
        locked and owned by the calling thread.

        Only call this method when the calling thread owns the lock. A
        RuntimeError is raised if this method is called when the lock is
        unlocked.

        There is no return value.

        """
        if self._owner != get_ident():
            raise RuntimeError("cannot release un-acquired lock")
        # 每次调用release,计数器减1
        self._count = count = self._count - 1
        if not count:
            self._owner = None
            self._block.release()

    def __exit__(self, t, v, tb):
        self.release()

    # Internal methods used by condition variables

    def _acquire_restore(self, state):
        self._block.acquire()
        self._count, self._owner = state

    def _release_save(self):
        if self._count == 0:
            raise RuntimeError("cannot release un-acquired lock")
        count = self._count
        self._count = 0
        owner = self._owner
        self._owner = None
        self._block.release()
        return (count, owner)

    def _is_owned(self):
        return self._owner == get_ident()
python3中Rlock的实现代码

 

Ps:在使用RLock的时候,要注意RLock.acquire和Rlock.release必须是成对出现的。否则会造成资源死锁,其他线程一直等待的情况,即“死锁”。

死锁发生的条件:

  • 互斥条件:线程对资源的访问是排他性的,如果一个线程对占用了某资源,那么其他线程必须处于等待状态,直到资源被释放。
  • 请求和保持条件:线程T1至少已经保持了一个资源R1占用,但又提出对另一个资源R2请求,而此时,资源R2被其他线程T2占用,于是该线程T1也必须等待,但又对自己保持的资源R1不释放。
  • 不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程剥夺,只能在使用完以后由自己释放。
  • 环路等待条件:在死锁发生时,必然存在一个“进程-资源环形链”,即:{p0,p1,p2,...pn},进程p0(或线程)等待p1占用的资 源,p1等待p2占用的资源,pn等待p0占用的资源。(最直观的理解是,p0等待p1占用的资源,而p1而在等待p0占用的资源,于是两个进程就相互等 待)

上面的文字太生硬,看一个比喻就懂了:

  迎面开来的汽车A和汽车B过马路,汽车A得到了半条路的资源(满足死锁发生条件1:资源访问是排他性的,我占了路你就不能上来,除非你爬我头上 去),汽车B占了汽车A的另外半条路的资源,A想过去必须请求另一半被B占用的道路(死锁发生条件2:必须整条车身的空间才能开过去,我已经占了一半,尼 玛另一半的路被B占用了),B若想过去也必须等待A让路,A是辆兰博基尼,B是开奇瑞QQ的屌丝,A素质比较低开窗对B狂骂:快给老子让开,B很生气,你 妈逼的,老子就不让(死锁发生条件3:在未使用完资源前,不能被其他线程剥夺),于是两者相互僵持一个都走不了(死锁发生条件4:环路等待条件),而且导 致整条道上的后续车辆也走不了。

 

2、活锁

活锁:是指线程1可以使用资源,但它很礼貌,让其他线程先使用资源,线程2也可以使用资源,但它很绅士,也让其他线程先使用资源。这样你让我,我让你,最后两个线程都无法使用资源。

一个比喻:马路中间有条小桥,只能容纳一辆车经过,桥两头开来两辆车A和B,A比较礼貌,示意B先过,B也比较礼貌,示意A先过,结果两人一直谦让谁也过不去。

3、饥饿

是指如果线程T1占用了资源R,线程T2又请求封锁R,于是T2等待。T3也请求资源R,当T1释放了R上的封锁后,系统首先批准了T3的请求,T2仍然等待。然后T4又请求封锁R,当T3释放了R上的封锁之后,系统又批准了T4的请求......,T2可能永远等待。

一个比喻:在“首堵”北京的某一天,天气阴沉,空气中充斥着雾霾和地沟油的味道,某个苦逼的临时工交警正在处理塞车,有两条道A和B上都堵满了车 辆,其中A道堵的时间最长,B相对相对堵的时间较短,这时,前面道路已疏通,交警按照最佳分配原则,示意B道上车辆先过,B道路上过了一辆又一辆,A道上 排队时间最长的确没法通过,只能等B道上没有车辆通过的时候再等交警发指令让A道依次通过,这也就是ReentrantLock显示锁里提供的不公平锁机 制(当然了,ReentrantLock也提供了公平锁的机制,由用户根据具体的使用场景而决定到底使用哪种锁策略),不公平锁能够提高吞吐量但不可避免 的会造成某些线程的饥饿。

4、同步阻塞

当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“同步阻塞”

 

线程池

python的线程池有两种实现方式,我们先来看一个比较简单的实现方式.
实现思路:

    1. 通过队列(先进先出队列,队列都是在内存中操作,进程退出,队列清空)来实现,线程池中没有线程时为阻塞状态.
    2. 自定义一个线程池类,构造方法时,创建一个指定元素数量的队列,队列中的元素为线程类
    3. 使用线程时,使用队列的get方法得到一个线程类,使用__call__方法创建一个线程,使用线程执行指定的程序
    4. 程序执行完成后,在队列中添加一个新的线程类
import threading,time,queue

class ThreadPool:
    def __init__(self,maxsize):
        self.maxsize=maxsize
        self._q=queue.Queue(maxsize)
        for i in range(maxsize):
            self._q.put(threading.Thread)

    def get_thread(self):
        return self._q.get()
    def add_thread(self):
        self._q.put(threading.Thread)

pool=ThreadPool(5)

def task(arg,p):
    print(arg)
    time.sleep(1)
    p.add_thread()

for i in range(100):
    t = pool.get_thread()   #线程池中没有线程为阻塞状态
    obj=t(target=task,args=(i,pool))
    obj.start()

此方式的缺点:

1. 线程池中线程不能被重用,一直被创建和取出

2. 线程池长度及所占内存资源有浪费,可能过多(如果任务数小于线程池大小)。如果线程执行比较快,可能只需要少量的线程就能完成

所以有了第二种方式.
第二种方式是也是使用队列,但队列中的元素为为一个个(函数名,函数参数,)的元组,创建一个线程组成的列表,线程轮流去队列中取到元组,分解后执行函数,然后取下一个函数.

import queue
import threading
import contextlib
import time

StopEvent = object()


class ThreadPool(object):

    def __init__(self, max_num, max_task_num = None):
        if max_task_num:
            self.q = queue.Queue(max_task_num)
        else:
            self.q = queue.Queue()
        self.max_num = max_num
        self.cancel = False
        self.terminal = False
        self.generate_list = []
        self.free_list = []

    def run(self, func, args, callback=None):
        """
        线程池执行一个任务
        :param func: 任务函数
        :param args: 任务函数所需参数
        :param callback: 任务执行失败或成功后执行的回调函数,回调函数有两个参数1、任务函数执行状态;2、任务函数返回值(默认为None,即:不执行回调函数)
        :return: 如果线程池已经终止,则返回True否则None
        """
        if self.cancel:
            return
        if len(self.free_list) == 0 and len(self.generate_list) < self.max_num:
            self.generate_thread()
        w = (func, args, callback,)
        self.q.put(w)

    def generate_thread(self):
        """
        创建一个线程
        """
        t = threading.Thread(target=self.call)
        t.start()

    def call(self):
        """
        循环去获取任务函数并执行任务函数
        """
        current_thread = threading.currentThread
        self.generate_list.append(current_thread)

        event = self.q.get()
        while event != StopEvent:

            func, arguments, callback = event
            try:
                result = func(*arguments)
                success = True
            except Exception as e:
                success = False
                result = None

            if callback is not None:
                try:
                    callback(success, result)
                except Exception as e:
                    pass

            with self.worker_state(self.free_list, current_thread):
                if self.terminal:
                    event = StopEvent
                else:
                    event = self.q.get()
        else:

            self.generate_list.remove(current_thread)

    def close(self):
        """
        执行完所有的任务后,所有线程停止
        """
        self.cancel = True
        full_size = len(self.generate_list)
        while full_size:
            self.q.put(StopEvent)
            full_size -= 1

    def terminate(self):
        """
        无论是否还有任务,终止线程
        """
        self.terminal = True

        while self.generate_list:
            self.q.put(StopEvent)

        self.q.empty()

    @contextlib.contextmanager
    def worker_state(self, state_list, worker_thread):
        """
        用于记录线程中正在等待的线程数
        """
        state_list.append(worker_thread)
        try:
            yield
        finally:
            state_list.remove(worker_thread)



# How to use

pool = ThreadPool(5)

def callback(status, result):
    # status, execute action status
    # result, execute action return value
    pass

def action(i):
    print(i)

for i in range(30):
    ret = pool.run(action, (i,), callback)

time.sleep(5)
print(len(pool.generate_list), len(pool.free_list))
print(len(pool.generate_list), len(pool.free_list))
pool.close()
pool.terminate()
线程池

 

进程

 python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程。Python提供了非 常好用的多进程包multiprocessing,只需要定义一个函数,Python会完成其他所有事情。借助这个包,可以轻松完成从单进程到并发执行的 转换。multiprocessing支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

基本使用

多进程的使用方法与多线程基本一模一样。只需要将对应的模块和方法,替换为multiprocessing.Process即可。这里不再做过多的说明和举例,线程里已经很具体了。

from multiprocessing import Process

def foo(arg):
    print('say hi',arg)


if __name__ == "__main__":

    for i in range(10):
        p = Process(target=foo,args=(i,))
        #p.daemon = True  # 等同于线程的threading.Thread.setDaemon
        p.start()
        #p.join()

out:
say hi 0
say hi 2
say hi 4
say hi 1
say hi 3
say hi 6
say hi 8
say hi 7
say hi 5
say hi 9
多进程的基本使用

进程锁

当多个进程需要访问共享资源的时候,Lock可以用来避免访问的冲突。

  同样的,进程锁也包含了其他类型,包括RLock,Semaphore(用来控制对共享资源的访问数量,例如池的最大连接数)。Event。同 线程也是一样的。所属模块不同而 已:multiprocessing.Lock(),multiprocessing.RLock(),multiprocessing.Semaphore(n) ,multiprocessing.Event()。 具体使用案例可以查看http://www.cnblogs.com/kaituorensheng/p/4445418.html  或者将上边讲到的线程案例进行修改。

进程间数据共享

默认情况下,进程间的数据是不可以进行共享的。但是可以通过以下三个方法进行数据共享:queues, Array, Manager.dict()

1)queues

from multiprocessing import Process
from multiprocessing import queues
import multiprocessing

def foo(i,arg):
    arg.put(i)
    print('say hi',i,arg.qsize())


if __name__ == "__main__":
    li = queues.Queue(20,ctx=multiprocessing) # 源码中执行了ctx.lock
    for i in range(10):
        p = Process(target=foo,args=(i,li,))
        p.start()

out:
say hi 0 1
say hi 2 2
say hi 1 4
say hi 3 4
say hi 6 7
say hi 5 7
say hi 4 7
say hi 7 8
say hi 9 9
say hi 8 10
multiprocessing.queues

2)Array(不常用)

数组:Array的特点:1.它的内存地址是连续的,而列表不是  2. 数组中元素的数据类型,在创建的时候就已经定义好了。3. 个数一定。在创建的时候就需要指定

from multiprocessing import Process
from multiprocessing import Array

def foo(i,arg):
    arg[i] = i + 100
    for item in arg:
        print(item) 
    print('================')

if __name__ == "__main__":
    li = Array('i', 5) # 创建一个最大包含5个元素的数组
    for i in range(5):
        p = Process(target=foo,args=(i,li,))
        p.start()

out:
100
0
0
0
0
================
100
101
0
0
0
================
100
101
102
0
0
================
100
101
102
103
0
================
100
101
102
103
104
================
multiprocessing.Array

代码中的"i"类型是包含在C中类型对应表中的:

 

'c': ctypes.c_char,  'u': ctypes.c_wchar,
'b': ctypes.c_byte,  'B': ctypes.c_ubyte,
'h': ctypes.c_short, 'H': ctypes.c_ushort,
'i': ctypes.c_int,   'I': ctypes.c_uint,
'l': ctypes.c_long,  'L': ctypes.c_ulong,
'f': ctypes.c_float, 'd': ctypes.c_double
类型对应表

 

3)Manager.dict(常用)

 

from multiprocessing import Process
from multiprocessing import Manager

def foo(i,arg):
    arg[i] = i + 100
    print(arg.values())

if __name__ == "__main__":
    obj = Manager()
    li = obj.dict()
    for i in range(5):
        p = Process(target=foo,args=(i,li,))
        #p.daemon = True
        p.start()
        p.join() 
out:
[100]
[100, 101]
[100, 101, 102]
[100, 101, 102, 103]
[100, 101, 102, 103, 104]
Manager.dict实现数据共享

 

 

进程池

 

 

 进程池在multiprocessing模块中已经定义好,只需要直接使用即可。主要包含2个方法apply和apply_async

进程池的使用

from multiprocessing import Pool
import time
def f1(arg):
    print(arg,'b')
    time.sleep(5)
    print(arg,'a')

if __name__ == "__main__":
    pool = Pool(5)
    # 定义30个任务
    for i in range(30):
        # pool.apply(func=f1,args=(i,))
        pool.apply_async(func=f1,args=(i,))

    # pool.close() # 等待所有的任务执行完毕后,修改进程状态为close。否则会阻塞到这里
    time.sleep(2)
    pool.terminate() # 立即终止全部子进程
    pool.join()  # 主进程在这里等待子进程全部执行完毕

out:
0 b
1 b
2 b
3 b
4 b  # 执行到一半已经被强制终止

Process finished with exit code 0
进程池的使用

apply(self, func, args=(), kwds={}) # 使用arg和kwds参数调用func函数,结果返回前会一直阻塞,这样就导致子进程会顺序执行,而不是并发执行

apply_async(self, func, args=(), kwds={}, callback=None,error_callback=None)#  apply()方法的一个变体,会返回一个结果对象。如果callback被指定,那么callback可以接收一个参数然后被调用,当结果准备好回调 时会调用callback,调用失败时,则用error_callback替换callback。 Callbacks应被立即完成,否则处理结果的线程会被阻塞。对比apply方法,该方法不会阻塞,类似线程的setDaemon

pool.close() # 阻止更多的任务提交到pool,待任务完成后,工作进程会退出

pool.terminate() # 不管任务是否完成,立即停止工作进程。在对pool对象进程垃圾回收的时候,会立即调用terminate()。

pool.join()  # wait工作线程的退出,在调用join()前,必须调用close() or terminate()。这样是因为被终止的进程需要被父进程调用wait(join等价与wait),否则进程会成为僵尸进程。

参考:https://docs.python.org/3/library/multiprocessing.html#module-multiprocessing.pool

 

协程

概念

     线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。

  协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。

  协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),适用于协程;

协程的实现

协程的实现主要借助2个模块,greenlet和gevent(内部调用greenlet)

import gevent
def fun1():
  print("www.baidu.com")  # 第一步
  gevent.sleep(0)
  print("end the baidu.com") # 第三步
def fun2():
  print("www.zhihu.com")  # 第二步
  gevent.sleep(0)
  print("end th zhihu.com") # 第四步
gevent.joinall([
  gevent.spawn(fun1),
  gevent.spawn(fun2),
])
gevent
import greenlet
def fun1():
  print("12") # 第一步
  gr2.switch()
  print("56")  # 第三步
  gr2.switch()
def fun2():
  print("34") # 第二步
  gr1.switch()
  print("78") # 第四步
gr1 = greenlet.greenlet(fun1)
gr2 = greenlet.greenlet(fun2)
gr1.switch()
greenlet
from gevent import monkey; monkey.patch_all()
import gevent
import urllib2

def f(url):
    print('GET: %s' % url)
    resp = urllib2.urlopen(url)
    data = resp.read()
    print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([
        gevent.spawn(f, 'https://www.python.org/'),
        gevent.spawn(f, 'https://www.yahoo.com/'),
        gevent.spawn(f, 'https://github.com/'),
])
遇到IO操作自动切换

event loop是协程执行的控制点, 如果你希望执行协程, 就需要用到它们。
event loop提供了如下的特性:
•注册、执行、取消延时调用(异步函数)
•创建用于通信的client和server协议(工具)
•创建和别的程序通信的子进程和协议(工具)
•把函数调用送入线程池中

import asyncio
async def cor1():
  print("COR1 start")
  await cor2()
  print("COR1 end")
async def cor2():
  print("COR2")
loop = asyncio.get_event_loop()
loop.run_until_complete(cor1())
loop.close()

out:
COR1 start
COR2
COR1 end
asyncio

最后三行是重点。
•asyncio.get_event_loop() : asyncio启动默认的event loop
•run_until_complete() : 这个函数是阻塞执行的,知道所有的异步函数执行完成,
•close() : 关闭event loop。

 

参考:

http://www.cnblogs.com/jishuweiwang/p/5660933.html

http://blog.csdn.net/songfreeman/article/details/50822795

感谢两位博主

posted @ 2016-07-24 07:27  unixfbi.com  阅读(486)  评论(0编辑  收藏  举报