python3 多线程编程

内容借鉴:

https://www.cnblogs.com/z-joshua/p/6409362.html
https://www.cnblogs.com/hoobey/p/6915638.html

线程的挂起与阻塞的:

挂起:一般是主动的,由系统或程序发出,甚至于辅存中去。(不释放CPU,可能释放内存,放在外存)

阻塞:一般是被动的,在抢占资源中得不到资源,被动的挂起在内存,等待某种资源或信号量(即有了资源)将他唤醒。(释放CPU,不释放内存)

另外,有一段话很形象:

 首先这些术语都是对于线程来说的。对线程的控制就好比你控制了一个雇工为你干活。你对雇工的控制是通过编程来实现的。
 挂起线程的意思就是你对主动对雇工说:“你睡觉去吧,用着你的时候我主动去叫你,然后接着干活”。
 使线程睡眠的意思就是你主动对雇工说:“你睡觉去吧,某时某刻过来报到,然后接着干活”。
 线程阻塞的意思就是,你突然发现,你的雇工不知道在什么时候没经过你允许,自己睡觉呢,但是你不能怪雇工,肯定你这个雇主没注意,本来你让雇工扫地,结果扫帚被偷了或被邻居家借去了,你又没让雇工继续干别的活,他就只好睡觉了。至于扫帚回来后,雇工会不会知道,会不会继续干活,你不用担心,雇工一旦发现扫帚回来了,他就会自己去干活的。因为雇工受过良好的培训。这个培训机构就是操作系统。
 
 挂起进程在操作系统中可以定义为暂时被淘汰出内存的进程,机器的资源是有限的,在资源不足的情况下,操作系统对在内存中的程序进行合理的安排,其中有的进程被暂时调离出内存,当条件允许的时候,会被操作系统再次调回内存,重新进入等待被执行的状态即就绪态,系统在超过一定的时间没有任何动作.

操作系统为什么要引入挂起状态?

挂起状态涉及到中级调度,因为当内存中的某个程序需要大的内存空间来执行,但这时内存有没有空余空间了,那么操作系统就回根据调度算法把一些进程放到外存中去,以腾出空间给正在执行的程序的数据和程序,所以引如了挂起状态。引起挂起状态的原因有如下几方面:

(1)终端用户的请求。当终端用户在自己的程序运行期间发现有可疑问题时,希望暂停使自己的程序静止下来。亦即,使正在执行的进程暂停执行;若此时用户进程正处于就绪状态而未执行,则该进程暂不接受调度,以便用户研究其执行情况或对程序进行修改。我们把这种静止状态成为“挂起状态”。

(2)父进程的请求。有时父进程希望挂起自己的某个子进程,以便考察和修改子进程,或者协调各子进程间的活动。

(3)负荷调节的需要。当实时系统中的工作负荷较重,已可能影响到对实时任务的控制时,可由系统把一些不重要的进程挂起,以保证系统能正常运行。

(4)操作系统的需要。操作系统有时希望挂起某些进程,以便检查运行中的资源使用情况或进行记账。

(5)对换的需要。为了缓和内存紧张的情况,将内存中处于阻塞状态的进程换至外存上。

下面再说下进程和线程的状态:

进程:一般大家认为是三种状态:运行、阻塞、就绪。也有分为五态的(多了创建和退出状态)

线程:一般认为是四种状态:New Thread(not alive)、Runnable Thread(alive)、Blocked Thread(alive)、Dead Thread(not alive)

多任务的实现有3种方式:

多进程模式;
多线程模式;
多进程+多线程模式

关于进程和线程,大家总结一句话是“进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元”。
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

差别

进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

线程可以分为:

内核线程:由操作系统内核创建和撤销。
用户线程:不需要内核支持而在用户程序中实现的线程。

Python3 线程中使用的两个模块为:

_thread---- 提供低级功能,现在已经甚少使用## 标题
threading(推荐使用) ----- 有_thread的功能都是还有许多高级功能,绝大部分情况下都是使用这个

_thread和threading,_thread是低级模块,threading是高级模块,对_thread进行了封装。绝大多数情况下,我们只需要使用threading这个高级模块。

创建线程的三个方法:

1,创建Thread实例,,将一个函数传给它
2,创建Thread实例,,传一个可调用的实例给它
3,派生Thread的子类,,并创建子类的实例 例: class 类名(threading.Thread):

启动线程

启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行。

threading.Thread

Thread 是threading模块中最重要的类之一,可以使用它来创建线程。有两种方式来创建线程:一种是通过继承Thread类,重写它的run方法;另一种是创建一个threading.Thread对象,在它的初始化函数 (init)中将可调用对象作为参数传入。
  
调用方法引入线程:

     1 import threading
     2 import time
     3 '''`在这里插入代码片`
     4 没有引入类,直接创建Thread实例,调用threading.Thread()方法
     5 '''
     6 def loop(i,sleep_time):
     7     print('loop:',i,'start')
     8     time.sleep(sleep_time)
     9     print('loop:',i,'done')
    10 def main():
    11     sleep_time = [4,2]
    12     loops = range(sleep_time.__len__())
    13     thread_list = []
    14     for i in loops:
    15         t = threading.Thread(target=loop,args=(i,sleep_time[i]))
    16         t.start()
    17         thread_list.append(t)
    18     for i in thread_list:
    19         i.join()#主线程等待所有子线程执行完毕再继续执行
    20     print('all thread have done!')
    21 
    22 if __name__ == "__main__":
    23     main()

调用Thread函数实现面向对象思想

 1 import threading
 2 import time
 3 '''
 4 引入类,实现了面向对象编程的思想,可扩展性强
 5 '''
 6 class threadFunc(object):
 7     def __init__(self,func,i,sleep_time):
 8         self.func = func
 9         self.i = i
10         self.sleep_time = sleep_time
11     def __call__(self, *args, **kwargs):#回调函数执行self.func(*args)
12         self.func(self.i,self.sleep_time)
13 def loop(i,sleep_time):
14     print('loop:', i, 'start')
15     time.sleep(sleep_time)
16     print('loop:', i, 'done')
17 def main():
18     loops = [4,2]
19     nloop = range(loops.__len__())
20     thread_list = []
21     for i in nloop:
22         t = threading.Thread(target=threadFunc(loop,i,loops[i]))
23         t.start()
24         thread_list.append(t)
25     for i in thread_list:
26         
27         i.join()
28     print('all thread have done!')
29 
30 if __name__ == '__main__':
31     main()

通过继承实现线程的创建

 1 import threading
 2 import time
 3 
 4 class Mythread(threading.Thread):
 5     def __init__(self,func,args,name = ''):
 6 
 7         '''
 8         官方文档
 9         If a subclass overrides the constructor, it must make sure to invoke
10         the base class constructor (Thread.__init__()) before doing anything
11         else to the thread.
12         如果子类重写构造函数,则必须确保在对线程执行任何其他操作之前调用基类构造函数(Thread._init_())。
13         '''
14         threading.Thread.__init__(self)#继承父类的__init()__方法很重要 #######
15         self.func = func
16         self.args = args
17         self.name = name
18     def run(self):
19         self.func(*self.args)
20 def loop(i,sleep_time):
21     print('loop:', i, 'start')
22     time.sleep(sleep_time)
23     print('loop:', i, 'done')
24 def main():
25     loops = [4,2]
26     thread_list = []
27     nloop = range(len(loops))
28     for i in nloop:
29         t = Mythread(loop,(i,loops[i]),loop.__name__)
30         t.start()
31         thread_list.append(t)
32     for i in thread_list:
33         i.join()
34     print('all have done!')
35 
36 
37 if __name__ == '__main__':
38     main()

Thread.join()

调用Thread.join将会使主调线程堵塞,直到被调用线程运行结束或超时。参数timeout是一个数值类型(thread.join(timeout=时间)),表示超时时间,如果未提供该参数,那么主调线程将一直堵塞到被调线程结束。

threading.Lock与RLock

为什么要用锁这个控制机制?在同一个进程中的资源,线程是共享的,如果不进行资源的合理分配,对数据造成破坏,使得线程运行的结果不可预期。这种现象称为“线程不安全”。所以不排除两个进程同时访问一个数据的情况发生,一旦发生就会造成数据修改错误。
  
  在threading模块中,定义两种类型锁:threading.Lock和threading.RLock。它们之间有一点细微的区别,通过比较下面两段代码来说明:

 1 import threading
 2 lock = threading.Lock() 
 3 #Lock对象
 4 lock.acquire()
 5 lock.acquire() 
 6 #产生了死琐。
 7 lock.release()
 8 lock.release()
 9   
10 import threading
11 rLock = threading.RLock() 
12 #RLock对象
13 rLock.acquire()
14 rLock.acquire() 
15 #在同一线程内,程序不会堵塞。
16 rLock.release()
17 rLock.release()

总结:Lock不允许在同一线程中多次被acquire(),RLock可以。但是RLock释放的时候也必须释放对应的之前的acquire次数。比如你之前acuire()了5次后面就必须要有5个release()才算真正释放RLock。

threading.Condition

可以把Condiftion理解为一把高级的锁,它提供了比Lock, RLock更高级的功能,允许我们能够控制复杂的线程同步问题。

threadiong.Condition在内部维护一个锁对象(默认是RLock),可以在创建Condigtion对象的时候把锁对象作为参数传入。

condition方法:

1.acquire():线程获得锁

2.release():释放锁

3.wait():线程挂起状态,会自动释放锁。

4.notify():通知(唤醒)其他阻塞的一个线程(如果有多个线程等待这个锁也只唤醒一个)获得锁,并不会释放锁

5.notifyALL():通知(唤醒)其他阻塞的线程获得锁,并不会释放锁

例子(捉迷藏)

#encoding:utf-8

import threading,time

class Seeker(threading.Thread):
    def __init__(self,cond,name):
        threading.Thread.__init__(self) ###
        self.cond=cond
        self.name=name

def run(self):
    self.cond.acquire()
    print ('1:我把眼睛蒙上了')
    self.cond.notify()
    self.cond.wait()


    print ('3:我找到你啦')
    self.cond.notify()
    self.cond.wait()


    print ('5:我赢了')
    self.cond.release()


class Hider(threading.Thread):
    def __init__(self,cond,name):
        threading.Thread.__init__(self) ####
        self.cond=cond
        self.name=name

def run(self):
    time.sleep(1)
    self.cond.acquire()
    print ('2:我已经藏好了')
    self.cond.notify()
    self.cond.wait()

    print('4:被你找到啦')
    self.cond.notify() 
    self.cond.release()



cond=threading.Condition()
seeker=Seeker(cond,'seeker')
hider=Hider(cond,'hider')
seeker.start()
hider.start()

运行结果:
1:我把眼睛蒙上了
2:我已经藏好了
3:我找到你啦
4:被你找到啦
5:我赢了

threading.Event

在初始情况下,event 对象中的信号标志被设置为假。如果有线程等待一个 event 对象,而这个 event 对象的标志为假,那么这个线程将会被一直阻塞直至该标志为

Event实现与Condition类似的功能,不过比Condition简单一点。它通过维护内部的标识符来实现线程间的同步问题。

Event.wait() : 堵塞线程,直到Event对象内部标识位被设为True或超时(如果提供了参数timeout)。

Event.clear() : 将标志位置于false状态。

Event.set() : 设置标志位为true

Event.isSet() : 判断标志位状态

event 对象的一个重要特点是当它被设置为真时会唤醒所有等待它的线程。如果你只想唤醒单个线程, 最好是使用信号量或者 Condition对象来替代。 event 对象最好单次使用,就是说,你创建一个 event 对象,让某个线程等待这个对象,一旦这个对象被设置为真,你就 应该丢弃它。尽管可以通过 clear() 方法来重 置 event 对象,但是很难确保安全地清理 event 对象并对它重新赋值。很可能会发生错 过事件、死锁或者其他问题(特别是,你无法保证重置 event 对象的代码会在线程再次等待这个 event 对象之前执行)。如果一个线程需要不停地重复使用 event 对象,你 最好使用 Condition 对象来代替。

例子红绿灯:

import threading
import time
def car(event):
    while True:
        if event.isSet():
            print ('绿灯或者黄灯亮,可以通行。')
            time.sleep(2)
        else:
            print ('红灯亮,禁止通行。')
            time.sleep(2)
        


def light(event):
    while True:
        event.clear()
        print('红灯亮')
        time.sleep(6)
        event.set()
        print('绿灯亮')
        time.sleep(4)
        print('黄灯亮')
        time.sleep(2)

def main():
    event=threading.Event()
    c=threading.Thread(target=car,args=(event,))
    l=threading.Thread(target=light,args=(event,))
    l.start()
    c.start()

if __name__=='__main__':    #要知道这个是什么意思,类似的还有__repr__,__init__等等
    main()

运行结果:

红灯亮
红灯亮,禁止通行。
红灯亮,禁止通行。
红灯亮,禁止通行。
绿灯亮
绿灯或者黄灯亮,可以通行。
绿灯或者黄灯亮,可以通行。
黄灯亮
绿灯或者黄灯亮,可以通行。

threading.BoundedSemaphore信号量(PV操作)

信号量是最古老的同步原语之一。它是一个计数器,当资源消耗时递减,当资源释放时递增。你可以认为信号量代表它们的资源可用或不可用。消耗资源使计数器递减的操作习惯上称为P() (来源于荷兰单词probeer/proberen),也称为wait、try、acquire、pend或procure。相对地,当一个线程对一个资源完成操作时,该资源需要返回资源池中。这个操作一般称为 V()(来源于荷兰单词 verhogen/verhoog),也称为 signal、increment、release、post、vacate。Python 简化了所有的命名,使用和锁的函数/方法一样的名字:acquire 和 release。信号量比锁更加灵活,因为可以有多个线程,每个线程拥有有限资源的一个实例。

猜你喜欢

转载自blog.csdn.net/qq_41992088/article/details/83720038