廖雪峰Python学习笔记(十七) threadlocal

ThreadLocal

多线程环境下,每个线程都有自己的数据,一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己使用,不会影响其它线程,全局变量还得加锁使用。
但局部变量在函数调用时,传递麻烦:

def process_student(name):
    std = Student(name)
    # std是局部变量,但是每个函数都要用它,因此必须传进去:
  do_task_1(std)
    do_task_2(std)

def do_task_1(std):
    do_subtask_1(std)
    do_subtask_2(std)

def do_task_2(std):
    do_subtask_2(std)
    do_subtask_2(std)

但这样很麻烦,用一个全局dict 存放 所有的Student对象 ,然后以thread自身作为key获得 线程对应的Student对象:

global_dict = {}

def std_thread(name):
    std = Student(name)
    # 把std放到全局变量global_dict中:
    global_dict[threading.current_thread()] = std
    do_task_1()
    do_task_2()

def do_task_1():
    # 不传入std,而是根据当前线程查找:
    std = global_dict[threading.current_thread()]
    ...

def do_task_2():
    # 任何函数都可以查找出当前线程的std变量:
    std = global_dict[threading.current_thread()]

教程说这样可行 但是丑 。。。
ThreadLocal 应运而生,不用查找dict,自动完成

import threading
class studeng(object):
    pass
#创建全局ThreadLocal对象:
local_school  = threading.local()
def process_student():
    #获取当前线程关联的student
    std = local_school.student
    print('Hello, %s(in  %s)' % (std,  threading.current_thread().name))
def process_thread(name):
    #绑定ThreadLocal的student
    local_school.student = name
    process_student()
#线程一 绑定函数process_thread, 作用于Alice,线程名是Rhread-A
t1 = threading.Thread(target=process_thread,
                      args=('Alice',), name='Rhread-A')
#线程一 绑定函数process_thread, 作用于Bob,线程名是Thread-B
t2 = threading.Thread(target=process_thread,
                      args=('Bob',), name='Thread-B')
t1.start()
t2.start()
t1.join()
t2.join()

分析:全局变量local_school就是一个ThreadLocal对象,每个 Thread对它都可以读写student属性,但互不影响。可以把local_school看成是全局碧昂量,但每个属性如local_schllo.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。
可以理解为全局变量local_school是一个dict,不但 可以用local_school.student,还可以绑定其它变量,如local_school.teacher等等。
ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求 ,用户身份信息等,这样一个线程的所有 调用到的处理函数都可以非常方便地访问这些资源。

进程 VS 线程

优缺点分析:
要实现多任务,一般会设计Master-Worker模式,Master负责分配任务,Worker负责执行任务,因此,多任务环境下,一般有一个Master多个Worker。
如果用多线程实现Master-Worker,主进程就是Master,其它进程就是Worker。
如果用多线程实现Master-Worker,主线程就是Master,其它线程就是Worker。

多进程模式组大的优点是稳定性高,一个子进程崩溃,不会影响主进程和 其他子进程。(当然主进程挂了所有进程就 全挂了,但是Master进程只负责分配任务,挂掉的概率低)著名 的APache最早就是采用多进程模式。
多进程的缺点是创建进程的代价大,在 Unix/Linux系统下 ,用fork 调用还行,在Windows下创建进程开销巨大。另外,操作系统 能同时运行的进程数也是有限的,在内存和CPU限制下,如果有几千个进程同时运行,操作系统连调度都会成问题。

多线程模式通常比多进程快一点,但也块不到哪去 ,而且多线程模式有一个致命的缺点——任何一个线程挂掉都可能直接 造成整个进程崩溃,因为所有线程共享进程的内存。在Windows上,如果一个线程执行的代码出了问题,你经常可以看到这样的提示:“该程序执行了非法操作,即将关闭”,其实就是某线程出现问题,导致操作系统强制结束整个进程。

在Windows下,多线程的效率比多进程要高,所以微软的IIS服务器默认采用多线程模式。由于多线程存在稳定性的问题,IIS的稳定性就不如Apache。为了缓解这个问题,IIS和Apache现在又有多进程+多线程的混合模式,真是把问题越搞越复杂。

线程切换
无论是多进程还是多线程,只要数量一多,效率就上不去。因为操作系统要保存现场环境再执行切换,后面还要返回断点,总之切换不是无代价的,是有开销的。
所以多任务一旦多到一个程度,就会消耗 掉系统所有的资源,结果效率急剧下降,所有任务都做不好。

计算密集型 VS IO密集型
是否采用多任务的第二个考虑是任务类型,可以把任务分为计算密集型和IO密集型。
计算密集型任务的特点是要进行大量的计算,消耗CPU的资源,比如计算圆周率、对视频高清解码,全靠 CPU的运算能力。这种计算密集型任务可以用 多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行效率就越低,所以计算密集型任务同时进行的数量应当等于CPU的核心数。
计算密集型任务由于主要消耗CPU资源,代码运行效率至关重要,Python这样的脚本语言运行效率很低,完全不适合计算密集型任务,这种任务最好用C来写。

第二种 任务的类型是IO 密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗少,任务的大部分时间都在等待IO操作完成(IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,如WEB应用。

IO密集型任务执行期间,99%的时间都花在IO上花在CPU上的时间很少,因此,用运行速度极快的C语言替换python,没法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,C语言最差。

异步IO
现代操作系统对IO操作已经做了巨大的改进,最大的特点就是支持异步IO。如果充分利用操作系统提供的异步IO支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型,Nginx就是支持异步IO的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。
在多核CPU上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。
由于系统总的进程数量十分有限,因此操作系统调度非常高效。用异步IO编程模型来实现多任务是一个主要的趋势。
对应到Python语言,单线程的异步编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值