Python学习总结【第十四篇】:Python之线程、进程和协程

Python线程

 Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元。

#!/usr/bin/env python3
# -*- coding:utf-8 -*-
import threading
import time
  
def show(arg):
    time.sleep(1)
    print('thread'+str(arg))

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

print('main thread stop')

# 结果:
main thread stop
thread0
thread2
thread1
thread4
thread3
thread8
thread5
thread9
thread6
thread7

上述代码创建了10个"前台"线程,然后控制器交给CPU,CPU根据指定算法进行调度,分片执行指令。

更多方法:

start        线程准备就绪,等待CPU调度
setName      为线程设置名称
getName      获取线程名称
setDaemon    设置为后台线程或前台线程(默认),如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止。
             如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止
join         逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义
run          线程被cpu调度后自动执行线程对象的run方法
import threading


def func(args):
    print(args)

t = threading.Thread(target=func, args=("123",))
t.setDaemon(True)       # 默认False True表示主线程不等此子线程
t.start()               # 不代表当前线程会被立即执行
# t.join()                # 表示主线程到此,等待....直到子线程执行完毕。  t.join(n) 带参数,表示主线程在此最多等待n秒


# func("123")
print("end")
print("end")
print("end")
print("end")
print("end")
print("end")
练习案例
# 方法1:一般正常创建线程
import threading


def f1(arg):
    print(arg)

t = threading.Thread(target=f1, args=(123,))
t.start()

# 方法2:通过自定义线程类创建线程
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()
创建线程的两种方式

 

线程锁(Lock,RLock)

  由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,当多个线程同时修改同一条数据时可能会出现脏数据,所以,出现了线程锁 - 同一时刻允许一个线程执行操作。

import time
import threading

NUM = 10


def f1():
    global NUM
    NUM -= 1
    time.sleep(2)
    print(NUM)

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

# 结果
0
0
0
0
0
0
0
0
0
0
未使用锁
import time
import threading

NUM = 10


def f1(l):
    l.acquire()
    global NUM
    NUM -= 1
    time.sleep(2)
    print(NUM)
    l.release()

# l = threading.Lock()  # 锁不能嵌套添加 RLock可以
lock = threading.RLock()

for i in range(10):
    t = threading.Thread(target=f1, args=(lock,))
    t.start()
# 结果:
9
8
7
6
5
4
3
2
1
0
使用锁

 

信号量(Semaphore)

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

#案例1
import threading
import time


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

if __name__ == '__main__':

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

# 结果
run the thread: 0
run the thread: 1
run the thread: 2
run the thread: 4
run the thread: 3

run the thread: 5
run the thread: 6
run the thread: 7
run the thread: 8
run the thread: 9

run the thread: 10
run the thread: 11
run the thread: 12
run the thread: 13
run the thread: 14

run the thread: 15
run the thread: 16
run the thread: 17
run the thread: 18
run the thread: 19

# 案例2
import threading
import time

NUM = 10


def func(i, l):
    global NUM
    l.acquire()
    NUM -= 1
    time.sleep(2)
    print(NUM, i)
    l.release()

lock = threading.BoundedSemaphore(5)
for j in range(30):
    t = threading.Thread(target=func, args=(j, lock,))
    t.start()
案例

 

事件(event)

  Python线程的事件用于主线程控制其他线程的执行,事件主要提供三个方法:set\wait\clear

事件处理机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果“Flag”值为True,那么event.wait 方法时便不再阻塞

  • clear:将“Flag”设置为False
  • set:将“Flag”设置为True
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import threading


def do(event):
    print('start')
    event.wait()
    print('execute')


event_obj = threading.Event()
for i in range(10):
    t = threading.Thread(target=do, args=(event_obj,))
    t.start()

event_obj.clear()
inp = input('input:')
if inp == 'true':
    event_obj.set()

# 结果:
start
start
start
start
start
start
start
start
start
start
input:true    # 手动输入
execute
execute
execute
execute
execute
execute
execute
execute
execute
execute
event案例
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()  # 设置成绿灯

# 结果:
0
1
2
3
4
5
6
7
8
9
>>>1
100
102
104
106
108
101
103
105
107
109
event上课案例

 

条件(Condition)

使得线程等待,只有满足某条件时,才释放n个线程

import threading
 
def run(n):
    con.acquire()
    con.wait()
    print("run the thread: %s" %n)
    con.release()
 
if __name__ == '__main__':
 
    con = threading.Condition()
    for i in range(10):
        t = threading.Thread(target=run, args=(i,))
        t.start()
 
    while True:
        inp = input('>>>')
        if inp == 'q':
            break
        con.acquire()
        con.notify(int(inp))
        con.release()
案例1
def condition_func():

    ret = False
    inp = input('>>>')
    if inp == '1':
        ret = True

    return ret


def run(n):
    con.acquire()
    con.wait_for(condition_func)
    print("run the thread: %s" %n)
    con.release()

if __name__ == '__main__':

    con = threading.Condition()
    for i in range(10):
        t = threading.Thread(target=run, args=(i,))
        t.start()
案例2

 

Timer

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

from threading import Timer

def hello():
    print("hello, world")

t = Timer(1, hello)
t.start()  
# 结果:等待1s后输出hello worl

自定义线程池

import queue
import threading
import time


class ThreadPool:
    """
    线程重用问题
    线程池直接开到最大,有可能造成浪费
    """
    def __init__(self, maxsize=5):
        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)  # 实例化一个可以存放5个对象的线程池


def task(arg, p):  # p 线程池对象
    print(arg)
    time.sleep(2)
    p.add_thread()      # 执行完任务以后,将令牌重新放回线程池


for i in range(100):
    # threading.Thread类
    t = pool.get_thread()
    obj = t(target=task, args=(i, pool,))
    obj.start()
自定义线程池(利用队列)
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Alex Li
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)


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(300):
    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))
自定义线程池(高级版本)

 补充知识点:上下文管理

import contextlib


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

free_list = []
current_thread = "alex"
with worker_state(free_list, current_thread):
    print(123)
    print(456)

# 通过debug查看程序执行过程


如何终止线程池操作? 详细分析自定义线程池(高级版本)的代码

生产者消费者模型

什么是生产这消费者模型?

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

  这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟悉一个设计模式。

import queue
import threading
import time


class ThreadPool:
    """
    线程重用问题
    线程池直接开到最大,有可能造成浪费
    """
    def __init__(self, maxsize=5):
        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)  # 实例化一个可以存放5个对象的线程池


def task(arg, p):  # p 线程池对象
    print(arg)
    time.sleep(2)
    p.add_thread()      # 执行完任务以后,将令牌重新放回线程池


for i in range(100):
    # threading.Thread类
    t = pool.get_thread()
    obj = t(target=task, args=(i, pool,))
    obj.start()
生产者消费者模型案例

引入生产者消费者的优势:
1)解耦
2)支持并发
3)支持忙闲不均问题

补充知识:队列

  queue模块是Python3中提供队列操作的模块,简单易用,现在我们来介绍队列的相关的知识及工作中常见的应用场景。

1、常见的几种队列讲解

Python中由四种队列分别是:

1)先进先出队列 queue.Queue

2)后进先出队列 queue.LifoQueue

3)优先级队列    queue.ProrityQueue

4)双向队列  queue.deque

2、各种队列详细讲解

import queue

q = queue.Queue(10)      # 创建一个队列对象,并设置队列长度为10,默认为0 代表队列可以无限长
print("队列大小:%s" % q.maxsize)    # 查看目前队列中值的长度
print("目前队列是否为空:%s" % q.empty())    # 判断队列是否为空,如果队列为空,返回True,反之False
for i in range(6):   
    q.put(i)                   # 将值放入队列中
print("队列目前已经存放数据:%s 条" % q.qsize())    
print("开始进行取数据操作")
for i in range(3):
    print("第%s次取值为:%s" % (i+1, q.get()))         # q.get()  从队列中取值

队列中常见的方法:

q.empty() 如果队列为空,返回True,反之False

q.full() 如果队列满了,返回True,反之False

q.get([block[, timeout]]) 获取队列,timeout等待时间

q.get_nowait() 相当q.get(False)  向队列插入值

q.join() 实际上意味着等到队列为空,再执行别的操作

q.put(item, block=True, timeout=None):

q.put_nowait(item) 相当q.put(item, False)

q.qsize() 返回队列的大小

q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号

import queue
q = queue.LifoQueue()
q.put(123)
q.put(456)
print(q.get())

# 结果:456 
后进先出队列
import queue

q = queue.PriorityQueue()
q.put((0, "alex"))
q.put((2, "ccd"))
q.put((3, "linux"))
q.put((1, "windows"))
q.put((5, "acd"))
print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())

# 结果
(0, 'alex')
(1, 'windows')
(2, 'ccd')
(3, 'linux')
(5, 'acd')
优先级队列
import queue

q = queue.deque()
q.append(123)
q.append(345)
q.append(222)
q.appendleft(456)
print(q.pop())
print(q.popleft())

# 结果
222
456
双向队列

补充知识:全局解释器锁(GIL)

  介绍多线程及线程锁,就不得不提及Python的GIL(全局解释器锁)。Python因为GIL的问题表贬不一,因为它在解释器的层面限制了程序在同一时间只有一个线程被CPU实际执行,而不管你的程序中实际开了多少条线程。所以我们经常能发现,Python中的多线程编程有时候效率还不如单线程,就是因为这个原因。

为什么要有GIL?

  作为解释型语言,Python的解释器必须做到既安全又高效。我们都知道多线程编程会遇到的问题。解释器要留意的是避免在不同的线程操作内部共享的数据。 同时它还要保证在管理用户线程时总是有最大化的计算资源。那么,不同线程同时访问时,数据的保护机制是怎样的呢?答案是解释器全局锁GIL。GIL对诸如 当前线程状态和为垃圾回收而用的堆分配对象这样的东西的访问提供着保护。

为什么不能去掉GIL?

  首先,在早期的python解释器依赖较多的全局状态,传承下来,使得想要移除当今的GIL变得更加困难。其次,对于程序员而言,仅仅是想要理解它的实现就需要对操作系统设计、多线程编程、C语言、解释器设计和CPython解释器的实现有着非常彻底的理解。
在1999年,针对Python1.5,一个“freethreading”补丁已经尝试移除GIL,用细粒度的锁来代替。然而,GIL的移除给单线程程 序的执行速度带来了一定的负面影响。当用单线程执行时,速度大约降低了40%。虽然使用两个线程时在速度上得到了提高,但这个提高并没有随着核数的增加而 线性增长。因此这个补丁没有被采纳。
  另外,在python的不同解释器实现中,如PyPy就移除了GIL,其执行速度更快(不单单是去除GIL的原因)。然而,我们通常使用的CPython占有着统治地位的使用量,所以,你懂的。
  在Python 3.2中实现了一个新的GIL,并且带着一些积极的结果。这是自1992年以来,GIL的一次最主要改变。旧的GIL通过对Python指令进行计数来确 定何时放弃GIL。在新的GIL实现中,用一个固定的超时时间来指示当前的线程以放弃这个锁。在当前线程保持这个锁,且当第二个线程请求这个锁的时候,当 前线程就会在5ms后被强制释放掉这个锁(这就是说,当前线程每5ms就要检查其是否需要释放这个锁)。当任务是可行的时候,这会使得线程间的切换更加可预测。

GIL对我们有什么影响?

最大的影响是我们不能随意使用多线程。要区分任务场景。
在单核cpu情况下对性能的影响可以忽略不计,多线程多进程都差不多。在多核CPU时,多线程效率较低。GIL对单进程和多进程没有影响。

实际应用场景建议

在IO密集型任务中使用多线程,在计算密集型任务中使用多进程。

相关GIL说明:

原文:Python's Hardest Problem
译文:Python 最难的问题

Python进程

from multiprocessing import Process
import threading
import time
  
def foo(i):
    print 'say hi',i
  
for i in range(10):
    p = Process(target=foo,args=(i,))
    p.start()

注意:由于进程之间的数据需要各自持有一份,所以创建进程需要的非常大的开销。

进程数据共享

进程各自持有一份数据,默认无法共享数据

如何实现数据共享?

from multiprocessing import Array
from multiprocessing import Process


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

if __name__ == "__main__":
    li = Array("i", 10)
    for j in range(10):
        p = Process(target=foo, args=(j, li,))
        p.start()
Array方式
from multiprocessing import Manager
from multiprocessing import Process
import time


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


if __name__ == '__main__':
    obj = Manager()
    li = obj.dict()
    for i in range(10):
        p = Process(target=foo, args=(i, li,))
        p.start()
        # p.join()
        time.sleep(1)
Manager.dict()共享数据方式
from multiprocessing import Manager
from multiprocessing import Process
import time


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


if __name__ == '__main__':
    obj = Manager()
    li = obj.dict()
    for i in range(10):
        p = Process(target=foo, args=(i, li,))
        p.start()
        # p.join()
        time.sleep(1)
queues方式
    '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
Array相关数据类型对应表

 

进程池

  进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用的进程为止。

进程池中有两个方法:

  • apply
  • apply_async
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from  multiprocessing import Process,Pool
import time
  
def Foo(i):
    time.sleep(2)
    return i+100
  
def Bar(arg):
    print arg
  
pool = Pool(5)
#print pool.apply(Foo,(1,))
#print pool.apply_async(func =Foo, args=(1,)).get()
  
for i in range(10):
    pool.apply_async(func=Foo, args=(i,),callback=Bar)
  
print 'end'
pool.close()
pool.join()#进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
案例

Python协程

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

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

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

greenlet

#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
 
from greenlet import greenlet
 
 
def test1():
    print 12
    gr2.switch()
    print 34
    gr2.switch()
 
 
def test2():
    print 56
    gr1.switch()
    print 78
 
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
案例1
import gevent
 
def foo():
    print('Running in foo')
    gevent.sleep(0)
    print('Explicit context switch to foo again')
 
def bar():
    print('Explicit context to bar')
    gevent.sleep(0)
    print('Implicit context switch back to bar')
 
gevent.joinall([
    gevent.spawn(foo),
    gevent.spawn(bar),
])
案例2
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/'),
])
案例3:遇到IO操作自动切换

 

作业:开发一个批量主机管理工具

需求:

  1. 可以对机器进行分组
  2. 可以对指定的一组或多组机器执行批量命令,分发文件(发送\接收)
  3. 纪录操作日志
posted @ 2016-07-01 22:55  每天进步一点点!!!  阅读(399)  评论(0编辑  收藏  举报