python多线程详解,python中的线程和进程,Python线程详解

python多线程详解,python中的线程和进程,Python线程详解

本文主要介绍Python线程解释。本文详细讲解了线程的各个方面的知识,比如线程基础知识,线程状态,线程同步(锁),线程通信(条件变量)等等。有需要的可以参考一下。

1. 线程基础

1.1. 线程状态

线程有五种状态,状态转换的过程如下图所示:

1.2. 线程同步(锁)

多线程的好处是可以同时运行多个任务(至少感觉是这样)。但是,当线程需要共享数据时,可能会出现数据不同步的情况。考虑一个情况,一个列表中所有元素都是0,线程‘set’从后向前把所有元素都改成1,线程‘print’负责从前向后读取列表并打印。然后,可能当线程' set '开始变化时,线程' print '会打印列表,输出变成一半0一半1,这就是数据不同步。为了避免这种情况,引入了锁的概念。

锁有两种状态:3354锁定和解锁。每当像“set”这样的线程想要访问共享数据时,它必须首先获得一个锁;如果另一个线程,比如' print ',已经被锁定,那么就让线程' set '暂停,也就是同步阻塞;等到线程“print”访问并释放锁,然后让线程“set”继续。经过这样的处理,在打印列表时,要么输出全0,要么输出全1,不会再出现半0半1的尴尬场景。

下图显示了线程和锁之间的交互:

1.3. 线程通信(条件变量)

但是,还有一个尴尬的情况:名单一开始就不存在;而是由线程“create”创建的。如果“设置”或“打印”在“创建”运行之前访问列表,将出现异常。使用锁可以解决这个问题,但是“设置”和“打印”将需要一个无限循环3354。他们不知道“创建”将在何时运行。运行后让'创建'通知'设置'和'打印'显然是更好的解决方案。因此,引入了条件变量。

条件变量允许诸如“set”和“print”等线程在不满足条件时等待(当列表为None时)。当条件满足时(列表已创建),将发送通知告诉“设置”和“打印”条件已满足。你该起床工作了。然后“设置”和“打印”将继续运行。

与线程条件变量的交互如下图所示:

1.4.线程运行和阻塞的状态转换

最后,看看线程运行和阻塞状态之间的转换。

拥塞有三种情况:

同步阻塞指的是竞争锁定的状态。当一个线程请求锁定时,就会进入这种状态,一旦成功获得锁定,就会回到运行状态。

等待是指等待其他线程通知的状态。线程获得条件锁定后,调用“Wait”就会进入这种状态。一旦其他线程给出通知,线程将进入同步阻塞状态,再次竞争条件锁定;

其他阻塞是指调用time.sleep()、anotherthread.join()或等待IO时的阻塞。在这种状态下,线程不会释放获取的锁。

温馨提示:如果你能理解这些内容,下一个题目就很容易了;而且,这些内容在大多数流行的编程语言中都是一样的。(意思是你得看懂_我觉得作者水平低,得看懂别人的教程)

2. thread

Python通过两个标准库thread和threading提供对线程的支持。Thread提供了一个低级的、原始的线程和一个简单的锁。

复制代码如下:

#编码:UTF-8

导入线程

导入时间

#在线程中执行的函数

定义函数():

对于范围(5)中的I:

打印'功能'

时间.睡眠(1)

#结束当前线程

#此方法等效于thread.exit_thread()

Thread.exit() #当func返回时,线程也将结束。

#启动一个线程,该线程立即开始运行

#此方法等效于thread.start_new_thread()

#第一个参数是方法,第二个参数是方法的参数。

Thread.start_new(func,())#方法没有参数时需要传入空元组。

#创建锁(LockType,不能直接实例化)

#此方法等效于thread.allocate_lock()

lock=thread.allocate()

#确定锁是锁定的还是释放的。

print lock.locked()

#锁通常用于控制对共享资源的访问

计数=0

#获取锁,成功获取锁后返回True。

#如果没有填充可选的超时参数,它将一直阻塞,直到获得锁。

#否则,超时后将返回False

if lock.acquire():

计数=1

#打开锁

lock.release()

# thread模块提供的线程将在主线程结束后同时结束。

时间.睡眠(6)

thread 模块提供的其他方法:

Thread.interrupt_main():在其他线程中终止主线程。

Thread.get_ident():获取表示当前线程的幻数,通常用于从字典中获取线程相关的数据。这个数字本身没有意义,在线程结束时会被新的线程重用。

Thread还提供了一个ThreadLocal类来管理与线程相关的数据,名为thread。_local,在线程中引用。

由于thread提供的线程函数不多,主线程结束后无法继续运行,不提供条件变量等原因,一般不使用Thread模块,这里就不介绍了。

3. threading

基于Java的线程模型的线程设计。锁和条件变量是Java中对象的基本行为(每个对象都有自己的锁和条件变量),而在Python中它们是独立的对象。Python线程提供了Java线程行为的子集;如果没有优先级和线程组,线程就不能被停止、挂起、恢复或中断。由Python在Java线程中部分实现的静态方法在线程中作为模块方法提供。

threading 模块提供的常用方法:

Threading.currentThread():返回当前线程变量。

Threading.enumerate():返回包含正在运行的线程的列表。运行是指开始后结束前的线程,不包括开始前和结束后的线程。

Threading.activeCount():返回正在运行的线程数,结果与len(threading.enumerate())相同。

threading模块提供的类:

线程、锁、Rlock、条件、[有界]信号量、事件、计时器、局部。

3.1. Thread

Thread是一个线程类,类似于Java。有两种方法可以使用它,要么直接传入要运行的方法,要么从Thread继承并覆盖run():

复制代码如下:

#编码:UTF-8

导入线程

#方法1:将要执行的方法作为参数传递给Thread的构造函数

定义函数():

打印“func()传递给线程”

t=螺纹。线程(target=func)

启动()

#方法2:从线程继承,重写run()

类MyThread(线程。线程):

定义运行(自身):

打印'从线程扩展的MyThread '

t=MyThread()

启动()

构造方法:

Thread(group=None,target=None,name=None,args=(),kwargs={})

Group:线程组,还没有实现。库引用中的提示必须是None;

目标:要执行的方法;

名称:线程名称;

Args/kwargs:要传递给方法的参数。

实例方法:

IsAlive():返回线程是否正在运行。运行是指启动后和终止前。

Get/setName(name):获取/设置线程名称。

Is/setDaemon(bool):获取/设置是否守护线程。初始值是从创建线程的线程继承的。当没有非守护线程仍在运行时,程序将终止。

Start():启动线程。

Join([timeout]):阻塞当前上下文的线程,直到调用此方法的线程终止或达到指定的超时(可选参数)。

使用join()的示例:

复制代码如下:

#编码:UTF-8

导入线程

导入时间

定义上下文(tJoin):

打印'在threadContext中'

tJoin.start()

# t上下文将被阻止,直到threadJoin终止。

tJoin.join()

# tJoin被终止并继续执行。

打印'输出线程上下文'

定义联接():

在threadJoin中打印

时间.睡眠(1)

打印出“螺纹接头”

tJoin=线程。线程(目标=连接)

tContext=线程。线程(target=context,args=(tJoin,))

tContext.start()

运行结果:

复制代码如下:

在threadContext中。

在threadJoin中。

out threadJoin

out threadContext

3.2. Lock

Lock(指令锁定)是可用的最低同步指令。当锁被锁定时,它不属于特定的线程。Lock包含两种状态,——锁定和解锁,以及两种基本方法。

可以认为Lock有一个锁池。当线程请求锁时,它将线程放入池中,直到它获得锁,然后离开池。在状态图中,池中的线程处于同步阻塞状态。

构造方法:

锁定()

实例方法:

Acquire([timeout]):将线程置于同步阻塞状态,并尝试获取锁。

Release():释放锁。线程必须在使用前被锁定,否则将引发异常。

复制代码如下:

#编码:UTF-8

导入线程

导入时间

数据=0

锁=线程。锁定()

定义函数():

全局数据

打印“%s获取锁.”% threading.currentThread()。getName()

#当调用acquire([timeout])时,线程将总是阻塞,

#直到获得锁或直到超时后秒(timeout参数是可选的)。

#返回是否获取了锁。

if lock.acquire():

打印“%s获取锁”% threading.currentThread()。getName()

数据=1

时间.睡眠(2)

打印“%s释放锁.”% threading.currentThread()。getName()

#调用release()将释放锁。

lock.release()

t1=线程。线程(target=func)

t2=线程。线程(target=func)

t3=线程。线程(target=func)

t1.start()

t2.start()

t3.start()

3.3. RLock

RLock(可重入锁)是一个同步指令,可以被同一个线程多次请求。RLock使用了“拥有线程”和“递归层次”的概念。当它被锁定时,RLock由一个线程拥有。带有RLock的线程可以再次调用acquire(),释放锁时需要调用release()相同的次数。

可以认为RLock包含一个锁池和一个初始值为0的计数器。每次成功调用acquire()/release()时,计数器会为1/-1,为0时,锁被解锁。

构造方法:

RLock()

实例方法:

acquire([超时])/release():类似于Lock。

复制代码如下:

#编码:UTF-8

导入线程

导入时间

rlock=线程。RLock()

定义函数():

#第一次请求锁定

打印“%s获取锁.”% threading.currentThread()。getName()

if rlock.acquire():

打印“%s获取锁”% threading.currentThread()。getName()

时间.睡眠(2)

#第二次请求锁定

打印“%s再次获取锁定.”% threading.currentThread()。getName()

if rlock.acquire():

打印“%s获取锁”% threading.currentThread()。getName()

时间.睡眠(2)

#第一次释放锁

打印“%s释放锁.”% threading.currentThread()。getName()

rlock.release()

时间.睡眠(2)

#第二次释放锁

打印“%s释放锁.”% threading.currentThread()。getName()

rlock.release()

t1=线程。线程(target=func)

t2=线程。线程(target=func)

t3=线程。线程(target=func)

t1.start()

t2.start()

t3.start()

3.4. Condition

条件通常与锁相关联。当需要在多个Contidion之间共享一个锁时,可以将一个Lock/RLock实例传递给构造函数,否则它会自己生成一个RLock实例。

可以认为,除了带锁的锁池,该条件还包含一个等待池,池中的线程在状态图中处于等待阻塞状态,直到另一个线程调用notify()/notifyAll()通知;收到通知后,线程进入锁池并等待锁。

构造方法:

条件([锁定/锁])

实例方法:

Acquire([timeout])/release():调用关联锁的对应方法。

Wait([timeout]):调用此方法将使线程进入等待条件池,等待通知并释放锁。线程必须在使用前被锁定,否则将引发异常。

Notify():调用这个方法会从等待池中挑选一个线程并通知它,收到通知的线程会自动调用acquire()尝试获取锁(进入锁池);其他线程仍在池中等待。调用此方法不会释放锁。线程必须在使用前被锁定,否则将引发异常。

NotifyAll():调用这个方法将通知等待池中的所有线程,所有这些线程将进入锁定池尝试获取锁。调用此方法不会释放锁。线程必须在使用前被锁定,否则将引发异常。

例子是非常常见的生产者/消费者模型:

复制代码如下:

#编码:UTF-8

导入线程

导入时间

#商品

产品=无

#条件变量

con=线程。条件()

#生产者方法

定义生产():

全球产品

if con.acquire():

虽然正确:

如果产品是无:

打印“生产.”字样

product='任何东西'

#告知消费者商品已经生产

con.notify()

#等待通知

con.wait()

时间.睡眠(2)

#消费者方法

定义消费():

全球产品

if con.acquire():

虽然正确:

如果产品不是无:

打印“消费.”

产品=无

#通知生产商货物已经没有了

con.notify()

#等待通知

con.wait()

时间.睡眠(2)

t1=线程。线程(目标=产生)

t2=线程。线程(目标=消费)

t2.start()

t1.start()

3.5. Semaphore/BoundedSemaphore

sehore(Semaphore)是计算机科学史上最古老的同步指令之一。Semaphore管理一个内置的计数器,每当调用acquire()时该计数器为-1,当调用release()时该计数器为1。计数器不能小于0;当计数器为0时,acquire()会将线程阻塞到同步锁定状态,直到其他线程调用release()。

基于这一特性,信号量经常被用来同步一些具有“来宾天花板”的对象,比如连接池。

BoundedSemaphore和Semaphore唯一的区别是,前者在调用release()时会检查计数器的值是否超过了计数器的初始值,如果超过了就会抛出异常。

构造方法:

信号量(value=1): value=1): Value是计数器的初始值。

实例方法:

获取([超时]):请求一个信号量。如果计数器为0,线程将被阻塞到同步阻塞状态;否则,计数器将为-1并立即返回。

Release():释放信号量,将计数器设置为1,如果使用了BoundedSemaphore,则检查释放的次数。release()方法不检查线程是否获得了信号量。

复制代码如下:

#编码:UTF-8

导入线程

导入时间

#计数器的初始值是2

信号量=线程。信号量(2)

定义函数():

#请求信号量,成功后计数器为-1;计数器为0时阻塞

打印“%s获取信号量.”% threading.currentThread()。getName()

if semaphore.acquire():

print“% s get semaphore“% threading . current thread()。getName()

时间.睡眠(4)

#释放信号量,计数器1

打印“%s”释放信号量“% threading.currentThread()。getName()

信号量. release()

t1=线程。线程(target=func)

t2=线程。线程(target=func)

t3=线程。线程(target=func)

t4=线程。线程(target=func)

t1.start()

t2.start()

t3.start()

t4.start()

时间.睡眠(2)

#没有得到信号量的主线程也可以调用release。

#如果使用了BoundedSemaphore,t4将在释放信号量时抛出异常。

打印“不获取主线程释放信号量”

信号量. release()

3.6. Event

事件是最简单的线程通信机制之一:一个线程通知事件,而其他线程等待事件。事件有一个内置的初始False标志,调用set()时设置为True,调用clear()时重置为False。Wait()将阻塞线程以等待阻塞状态。

事件实际上是条件的简化版本。没有事件锁,所以线程不能进入同步阻塞状态。

构造方法:

事件()

实例方法:

IsSet():当内置标志为真时返回真。

Set():将标志设置为True,通知所有处于等待阻塞状态的线程恢复运行。

Clear():将标志设置为False。

Wait([timeout]):如果标志为真,则立即返回;否则,该线程将被阻塞,以等待其他线程调用set()。

复制代码如下:

#编码:UTF-8

导入线程

导入时间

事件=线程。事件()

定义函数():

#等待事件,进入等待阻塞状态。

打印“%s等待事件.”% threading.currentThread()。getName()

event.wait()

#收到事件后进入运行状态。

打印“%s接收事件”% threading.currentThread()。getName()

t1=线程。线程(target=func)

t2=线程。线程(target=func)

t1.start()

t2.start()

时间.睡眠(2)

#发送事件通知

打印“主线程设置事件”

event.set()

3.7. Timer

Timer(定时器)是Thread的派生类,用于在指定时间后调用一个方法。

构造方法:

计时器(interval,function,args=[],kwargs={})

间隔:指定的时间

函数:要执行的方法

args/kwargs:方法的参数

实例方法:

计时器从线派生,没有增加实例方法。

复制代码代码如下:

#编码:UTF-8

导入线程

定义函数():

打印“你好定时器!”

定时器=线程。定时器(5,func)

timer.start()

3.8. local

当地的是一个小写字母开头的类,用于管理线程本地(线程局部的)数据。对于同一个本地,线程无法访问其他线程设置的属性;线程设置的属性不会被其他线程设置的同名属性替换。

可以把当地的看成是一个"线程-属性字典"的字典,本地封装了从自身使用线程作为键检索对应的属性字典、再使用属性名作为键检索属性值的细节。

复制代码代码如下:

#编码:UTF-8

导入线程

local=threading.local()

local.tname='main '

定义函数():

local.tname='notmain '

打印本地. tname

t1=线程。线程(目标=函数)

t1.start()

t1.join()

打印本地. tname

熟练掌握线程、锁、条件就可以应对绝大多数需要使用线程的场合,某些情况下当地的也是非常有用的东西。本文的最后使用这几个类展示线程基础中提到的场景:

复制代码代码如下:

#编码:UTF-8

导入线程

列表=无

条件=线程。条件()

def doSet():

if condition.acquire():

而列表是空的:

condition.wait()

对于范围内的I(len(list))[:-1]:

列表[i]=1

条件。释放()

def doPrint():

if condition.acquire():

而列表是空的:

condition.wait()

因为我列出了:

打印我,

打印

条件。释放()

def doCreate():

全球列表

if condition.acquire():

如果列表为无:

列表=[范围(10)中的我为0]

condition.notifyAll()

条件。释放()

tset=线程。线程(target=doSet,name='tset ')

tprint=线程。线程(target=doPrint,name='tprint ')

tcreate=线程。线程(target=doCreate,name='tcreate ')

tset.start()

tprint.start()

tcreate.start()

全文完

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: