Python 进程和线程 02 ThreadLocal、进程和线程、分布式进程

1 ThreadLocal

在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。但是局部变量也有问题,就是在函数调用的时候,传递起来很麻烦。

ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。

ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。

import threading

# 创建全局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()

t1 = threading.Thread(target=process_thread, args=('lisi',), name='thread-a')
t2 = threading.Thread(target=process_thread, args=('zhangsan',), name='thread-b')

t1.start()
t2.start()
t1.join()
t2.join()

运行结果:

hello, lisi (in thread-a)
hello, zhangsan (in thread-b)

2 进程 vs 线程

多进程:

  • 优点:稳定性高,因为一个子进程崩溃了,不会影响主进程和其他子进程。
  • 缺点:创建进程的代价大。

多线程:

  • 优点:多线程模式通常比多进程快一点。
  • 缺点:不稳定,任何一个线程挂掉都可能直接造成整个进程崩溃。

计算密集型:

  • 计算密集型任务的特点是要进行大量的计算,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。
  • 计算密集型任务可以用多任务完成,但是任务越多,任务切换的时间就越多,CPU执行任务的效率就越低。
  • 最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数。
  • Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。

IO密集型:

  • IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。
  • IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。
  • 对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。

3 分布式进程

在Thread和Process中,应当优选Process,因为Process更稳定,而且,Process可以分布到多台机器上,而Thread最多只能分布到同一台机器的多个CPU上。

Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。

Python的分布式进程接口简单,封装良好,适合需要把繁重任务分布到多台机器的环境下。

注意:Queue的作用是用来传递任务和接收结果,每个任务的描述数据量要尽量小。比如发送一个处理日志文件的任务,就不要发送几百兆的日志文件本身,而是发送日志文件存放的完整路径,由Worker进程再去共享的磁盘上读取文件。

task_master.py

import random,time,queue
from multiprocessing.managers import BaseManager

# 发送任务的队列
task_queue=queue.Queue()
# 接收结果的队列
result_queue=queue.Queue()

# 从BaseManager继承的QueueManager
class QueueManager(BaseManager):
    pass

# 把两个Queue都注册到网络上,callable参数关联了queue对象
QueueManager.register('get_task_queue',callable=lambda :task_queue)
QueueManager.register('get_result_queue',callable=lambda :result_queue)
# 绑定端口5000,设置验证码'abc'
manager=QueueManager(address=('',5000),authkey=b'abc')
# 启动queue
manager.start()
# 获得通过网络访问的queue对象
task=manager.get_task_queue()
result=manager.get_result_queue()
# 放几个任务进去
for i in range(10):
    n=random.randint(0,1000)
    print('put task %d...' % n)
    task.put(n)

# 从result队列读取结果
print('try get results...')
for i in range(10):
    r=result.get(timeout=10)
    print('result: %s' %r)
# 关闭
manager.shutdown()
print('master exit.')

task_worker.py

import time, sys, queue
from multiprocessing.managers import BaseManager


# 创建类似的QueueManager
class QueueManager(BaseManager):
    pass


# 从网络上获取queue,所以注册时只提供名字
QueueManager.register('get_task_queue')
QueueManager.register('get_result_queue')

# 连接到服务器,也就是运行task_master的机器
server_addr = '127.0.0.1'
print('connect to server %s...' % server_addr)
# 端口和验证码注意保持与master设置的一致
m = QueueManager(address=(server_addr, 5000), authkey=b'abc')
# 从网络连接:
m.connect()
# 获取queue对象
task = m.get_task_queue()
result = m.get_result_queue()
# 从task队列取任务,并把结果写入result队列
for i in range(10):
    try:
        n = task.get(timeout=1)
        print('run task %d * %d' % (n, n))
        r = '%d * %d = %d' % (n, n, n * n)
        time.sleep(1)
        result.put(r)
    except queue.Empty:
        print('task queue is empty.')
# 处理结果
print('worker exit.')

猜你喜欢

转载自blog.csdn.net/lihaogn/article/details/81286277
今日推荐