Python学习笔记十六:多线程

多线程编程技术可以实现代码并行,优化处理能力,同时可以将代码划分为功能更小的模块,使代码的可重用性更好。

1、线程和进程

1)进程

进程是程序的一次运行。每个进程都有自己的地址空间、内存、数据栈以及记录运行轨迹的辅助数据,操作系统管理运行的所有进程,并为这些进程公平分配时间。进程可以通过fork和spawn操作完成其他任务。因为各个进程都有自己的内存空间、数据栈等,所以只能使用进程间通信(IPC),而不能直接共享信息。

2)线程

线程跟进程不同,所有线程运行在同一个进程中,共享运行环境。

线程有开始、顺序执行和结束三部分,有一个自己的指令指针,记录运行到什么地方。线程的运行可能被抢占(中断)或暂时被挂起(睡眠),从而让其他线程运行,这叫做让步。一个线程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便地共享数据和相互通信。

线程一般是并发执行的。正式由于这种并行和数据共享的机制,使得多个任务的合作变得可能。实际上,在单CPU系统中,真正的并发运行并不可能,每个线程会被安排成每次只运行一小会,然后就把CPU让出来,让其他线程运行。

在进程的整个运行过程中个,每个线程都只做自己的事,需要时再跟其他线程共享运行结果。多个线程共同访问同一片数据不是完全没有危险的,由于数据访问的顺序不一样,因此有可能导致数据结果不一致的问题,这叫做竞态条件。大多数线程库都带有一系列同步原语,用于控制线程的执行和数据的访问。

3)多线程和多进程

什么叫做“多任务”呢?简单来说,就是系统可以同时运行多个任务。
对于操作系统来说,一个任务就是一个进程,开启多个任务就是多进程。
在一个进程内部,要同时做多件事,就需要同时运行多个线程。
多线程类似于同时执行多个不同的程序,多线程运行的优点:

  • 使用线程可以把占据长时间的程序中的任务放到后台去处理;
  • 用户界面可以更加吸引人,比如用户单击一个按钮,用于触发某些事件的处理,可以弹出一个进度条显示处理的进度;
  • 程序的运行速度可能加快;

在实现一些等待任务时,使用多线程更加有用,在这种情况下,我们可以释放一些珍贵资源(内存占用)。

线程在执行过程中与进程还是有区别的。每个独立线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能独立执行,必须依存在进程中,由进程提供多个线程执行控制。

由于每个进程至少要干一件事,因此一个进程至少有一个线程。多线程的执行方式和多进程是一样的,也是由操作系统在多个线程之间快速切换,让每个线程都短暂交替运行,看起来就像同时执行一样。

同时执行多个任务时,各个任务之间并不是没有关联的,而是需要相互通信和协调,有时任务1必须暂停等待任务2完成后才能继续执行,有时任务3和任务4不能同时执行。
总之,多线程是多个相互关联的线程的组合,多进程是多个相互独立的进程的组合。线程是最小的执行单元,进程至少由一个线程组成。

2、使用线程

1)全局解释器锁

Python代码的执行由Python虚拟机(解释器主循环)控制。Python在设计之初就考虑到在主循环中只能有一个线程执行,虽然Python解释器中可以“运行”多个线程,但是在任意时刻只有一个线程在解释器中运行。

Python虚拟机的访问由全局解释器锁的(GIL)控制,这个锁能保证同一时刻只有一个线程运行。

在多线程环境中,Python虚拟机按一下方式执行:

  • 设置GIL;
  • 切换到一个线程执行;
  • 运行指定数量的字节码指令或线程主动让出控制(可以调用time.sleep(0));
  • 把线程设置为睡眠模式;
  • 解锁GIL
  • 在此重复以上所有操作。

在调用外部代码时,GIL将被锁定。直到这个函数结束为止,编写扩展的程序员可以主动解锁GIL。

2)退出线程

当一个线程结束计算,它就退出了。线程可以调用_thread.exit()等退出函数,也可以使用Python退出线程的标准方法(如sys.exit()或抛出一个SystemExit异常),不过不可以直接“杀掉”(kill)一个线程。

不建议使用_thread模块。很明显的一个原因是,当主线程退出时,其他线程如果没有被清除就会退出。另一个模块threading能确保所有“重要的”子线程都退出后,进程才会结束。

3)Python的线程模块

Python提供了几个用于多线程编程的模块,包括_thread、threading和Queue等。_thread和threading模块允许程序员创建和管理线程。_thread模块提供了基本线程和锁的支持,threading提供了更高级别、功能更强的线程管理功能。Queue模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。

避免使用_thread模块,原因如下:

  • 首先:更高级别的threading模块更为先进,对线程的支持更为完善,而且使用_thread模块里的属性有可能与threading冲突;
  • 其次,低级别的_thread模块的同步原语很少(实际上还只有一个),而threading模块有很多;
  • 再者,_thread模块中在主线程结束时,所有线程都会被强制结束,没有警告也不会有正常清除工作,至少threading模块能确保重要子线程退出后进程才退出。

3、_thread模块

Python中调用_thread模块中的start_new_thread()函数产生新线程。_thread的语法如下:

_thread.start_new_thread( function , agrs [ , kwargs ])

其中,function为线程函数;agrs为传递给线程函数的参数,必须是元组(tuple)类型;kwargs为可选参数。

_thread模块除了产生线程外,还提供基本同步数据结构锁对象。同步原语与线程管理是密不可分的。

import _thread
from time import sleep
from datetime import datetime

date_time_formt = ‘%y - %M - %d %H:%M:%S’

def date_time_str(date_time):
     return datetime.strftime(date_time,date_time_formt)

def loop_one():
     print(‘+++线程1开始于:’ , date_time_str(datetime.now()))
     print(‘+++线程1休眠4秒’)
     sleep(4)
     print(‘+++线程1休眠结束,结束于:’ , data_time_str(datetime.now())))

def loop_two():
     print(‘+++线程2开始于:’ , date_time_str(datetime.now()))
     print(‘+++线程2休眠2秒’)
     sleep(2)
     print(‘+++线程2休眠结束,结束于:’ , data_time_str(datetime.now())))

def main():
    print(‘---所有线程开始时间:’ , date_time_str(datetime.now()))
    _thread.start_new_thread(loop_one , ())
    _thread.start_new_thread(loop_two , ())
    sleep(6)
    print(‘---所有线程结束时间:’ , date_time_str(datetime.now()))

if _name_ == ‘_main_’:
    main()

---所有线程开始时间:16-44-06 21:44:05
+++线程1开始于:16-44-06 21:44:05
+++线程1休眠4秒
+++线程2开始于:16-44-06 21:44:05
+++线程2休眠2秒
+++线程2休眠结束,结束于:16-44-06 21:44:07
+++线程1休眠结束,结束于:16-44-06 21:44:09
---所有线程结束时间:16-44-06 21:44:11

_thread模块提供了简单的多线程机制,两个循环并发执行,总的运行时间为最慢的线程的运行时间(主线程6 s),而不是所有线程的运行时间之和。start_new_thread()要求至少传两个参数,即使想要运行的函数不要参数,也要传一个空元组。

使用线程锁的代码为:

import _thread
from time import sleep
from datetime import datetime

loops = [4,2]
date_time_formt = ‘%y - %M - %d %H:%M:%S’

def date_time_str(date_time):
     return datetime.strftime(date_time,date_time_formt)

def loop (n_loop , n_sec ,lock):
     print(‘线程 (‘ , n_loop,’)开始执行:’)
           date_time_str(datetime.now() , ‘先休眠 (‘ , n_sec ,’) 秒’)
     sleep(n_sec)
     print(‘线程(‘ , n_loop,’) 休眠结束,结束于:’ , data_time_str(datetime.now())))
     lock.release

def main():
    print(‘---所有线程开始执行。。。’)
    locks = []
    n_loops = range(len(loops))

    for i in n_loops:
         lock = _thread.allocate_lock()
         lock.acquire()
         lock.qppend(lock)

    for i in n_loops:
          _thread.strat_new_thread(loop , (i , loops[i] , locks[i])) 

    for i in n_loops:
         while locks[i].locked():pass  

    print(‘---所有线程执行结束:’ , date_time_str(datetime.now()))

if _name_ == ‘_main_’:
    main()

---所有线程开始执行。。。
线程(1)开始执行:16-44-06 21:44:11 , 先休眠(2)秒
线程(0)开始执行:16-44-06 21:44:11 , 先休眠(4)秒
线程(1)休眠结束,结束于:16-44-06 21:44:13
线程(0)休眠结束,结束于:16-44-06 21:44:15
---所有线程执行结束:16-44-06 21:44:15

4、Threading模块

更高级别的threading模块不仅提供了Thread类,还提供了各种非常好用的同步机制。

_thread模块不支持守护线程,当主线程退出时,所有子线程无论是否在工作,都会被强行退出。threading模块支持守护线程,守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求,就一直等着。如果设定一个线程为守护线程,就表示这个线程不重要,在进程退出时,不用等待这个线程退出。如果主线程退出时不用等待子线程完成,就要设定这些线程的deamon属性,即在线程Thread.start()开始前,调用setDaemon()函数设定线程的daemon标志(Thread.setDaemon(True)),表示这个线程“不重要”。如果不一定要等待子线程执行完成再退出主线程,就什么都不用做或显式调用Thread.setDaemon(Flase)以保证daemon标志为Flase,可以调用Thread.isDaemon()函数判断daemon标志的值。新的子线程会继承父线程的daemon标志,整个Python在所有非守护线程退出后才会结束,即进程中没有非守护线程存在时才结束。

threading的Thread类

Thread有很多_thread模块里没有的函数。

猜你喜欢

转载自blog.csdn.net/viatorsun/article/details/80257337