python-多线程

一、简介

多线程编程对于具有如下特点的编程任务而言是非常理想的:本质上是异步的;需要多个并发活动;每个活动的处理顺序可能是不确定的,或者说是随机的、不可预测的。这种编程任务可以被组织或划分成多个执行流,其中每个执行流都有一个指定要完成的任务。根据应用的不同,这些子任务可能需要计算出中间结果,然后合并为最终的输出结果。

• UserRequestThread:负责读取客户端输入,该输入可能来自 I/O 通道。程序将创建多个线程,每个客户端一个,客户端的请求将会被放入队列中。
• RequestProcessor:该线程负责从队列中获取请求并进行处理,为第 3 个线程提供输出。
• ReplyThread:负责向用户输出,将结果传回给用户(如果是网络应用),或者把数据写到本地文件系统或数据库中。

二、线程和进程

1、进程

计算机程序只是存储在磁盘上的可执行二进制(或其他类型)文件。只有把它们加载到内存中并被操作系统调用,才拥有其生命期。进程(有时称为重量级进程)则是一个执行中的程序。每个进程都拥有自己的地址空间、内存、数据栈以及其他用于跟踪执行的辅助数据。操作系统管理其上所有进程的执行,并为这些进程合理地分配时间。进程也可以通过派生(fork 或 spawn)新的进程来执行其他任务,不过因为每个新进程也都拥有自己的内存和数据栈等,所以只能采用进程间通信(IPC)的方式共享信息。

2、线程

线程(有时候称为轻量级进程)与进程类似,不过它们是在同一个进程下执行的,并共享相同的上下文。可以将它们认为是在一个主进程或“主线程”中并行运行的一些“迷你进程”。

线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录当前运行的上下文。当其他线程运行时,它可以被抢占(中断)和临时挂起(也称为睡眠) ——这种做法叫做让步(yielding)。

一个进程中的各个线程与主线程共享同一片数据空间,因此相比于独立的进程而言,线程间的信息共享和通信更加容易。线程一般是以并发方式执行的,正是由于这种并行和数据共享机制,使得多任务间的协作成为可能。当然,在单核 CPU 系统中,因为真正的并发是不可能的,所以线程的执行实际上是这样规划的:每个线程运行一小会儿,然后让步给其他线程(再次排队等待更多的 CPU 时间)。在整个进程的执行过程中,每个线程执行它自己特定的任务,在必要时和其他线程进行结果通信。

当然,这种共享并不是没有风险的。如果两个或多个线程访问同一片数据,由于数据访问顺序不同,可能导致结果不一致。这种情况通常称为竞态条件(race condition)。幸运的是,大多数线程库都有一些同步原语,以允许线程管理器控制执行和访问。

三、全局解释器锁

Python 代码的执行是由 Python 虚拟机(又名解释器主循环)进行控制的。尽管 Python 解释器中可以运行多个线程,但是在任意给定时刻只有一个线程会被解释器执行。对 Python 虚拟机的访问是由全局解释器锁(GIL)控制的。这个锁就是用来保证同时只能有一个线程运行的。在多线程环境中, Python 虚拟机将按照下面所述的方式执行。
1.设置 GIL。
2.切换进一个线程去运行。
3.执行下面操作之一。
      a.指定数量的字节码指令。
      b.线程主动让出控制权(可以调用 time.sleep(0)来完成)。
4.把线程设置回睡眠状态(切换出线程)。
5.解锁 GIL。
6.重复上述步骤。

四、退出线程

当一个线程完成函数的执行时,它就会退出。另外,还可以通过调用诸如 thread.exit()之类的退出函数,或者 sys.exit()之类的退出 Python 进程的标准方法,亦或者抛出 SystemExit异常,来使线程退出。不过,你不能直接“终止”一个线程。

主线程应该做一个好的管理者,负责了解每个单独的线程需要执行什么,每个派生的线程需要哪些数据或参数,这些线程执行完成后会提供什么结果。这样,主线程就可以收集每个线程的结果,然后汇总成一个有意义的最终结果。

五、不使用线程的情况

我们将使用 time.sleep()函数来演示线程是如何工作的。 time.sleep()函数需要一个浮点型的参数,然后以这个给定的秒数进行“睡眠”,也就是说,程序的执行会暂时停止指定的时间。

import time

def loop():
    print('start loop() at:', time.ctime())
    time.sleep(4)
    print('loop() done at:', time.ctime())

def main():
    print('start at:', time.ctime())
    loop()
    print('done at:', time.ctime())

if __name__ == '__main__':
    main()

四、Python 的 thread 模块

除了派生线程外, thread 模块还提供了基本的同步数据结构,称为锁对象(lock object,也叫原语锁、 简单锁、 互斥锁、 互斥和二进制信号量)。thread 模块的核心函数是 start_new_thread()。它的参数包括函数(对象)、函数的参数以及可选的关键字参数。将专门派生新的线程来调用这个函数。

start_new_thread()必须包含开始的两个参数,于是即使要执行的函数不需要参数,也需要传递一个空元组。注意第一个参数仅为函数名,第二个参数是函数使用的变量名。

import time
import _thread

def loop():
    print('start loop() at:', time.ctime())
    time.sleep(4)
    print('loop() done at:', time.ctime())

def main():
    print('start at:', time.ctime())
    _thread.start_new_thread(loop,())
    time.sleep(4)
    print('done at:', time.ctime())

if __name__ == '__main__':
    main()

这个应用程序中剩下的一个主要区别是增加了一个 sleep(4)调用。为什么必须要这样做呢?这是因为如果我们没有阻止主线程继续执行,它将会继续执行下一条语句,显示“done”然后退出,而 loop这两个线程将直接终止。我们没有写让主线程等待子线程全部完成后再继续的代码,即我们所说的线程需要某种形式的同步。在这个例子中,调用 sleep()来作为同步机制。

通过引入锁,是比在主线程中额外延时 6 秒更好的线程管理方式。

import time
import _thread

loops = [4,2]


def loop(nloop,nsec,lock):
    print('start loop:{0} at:{1}'.format(nloop,time.ctime()))
    time.sleep(nsec)
    print('loop:{0} done at:{1}'.format(nloop,time.ctime()))
    lock.release()


def main():
    print("starting at:{0}".format(time.ctime()))
    locks = []
    nloops = range(len(loops))

    for i in nloops:
        lock = _thread.allocate_lock()  # 获得线程的本地锁
        lock.acquire()  # 开始加锁,获得锁并加锁
        locks.append(lock)  # 项当前的锁集合中添加该锁

    for i in nloops:
        _thread.start_new(loop, (i, loops[i], locks[i]))  # 启动2个线程用来执行loop函数并传递参数

    # 反复检查锁是否被锁住,如果被锁住就一直死循环,否者停止循环检查
    for i in nloops:
        while locks[i].locked(): pass  # 最后会阻塞当前的线程,反复检查当前的锁是否被锁住,如果被锁住就暂停等待解锁,才能#让主线程停止
    # 当所有的线程都执行完毕后就会执行最后的打印
    print("all DONE at:{0}".format(time.ctime()))


if __name__ == '__main__':
    main()

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值