zoukankan      html  css  js  c++  java
  • Day12- Python基础12 线程、GIL、Lock锁、RLock锁、Semaphore锁、同步条件event

    http://www.cnblogs.com/yuanchenqi/articles/6248025.html  博客地址

    本节内容:

    1:进程和线程的说明

    2:线程的两种调用方式

    3:threading.thread的实例方法

    4:python的GIL

    5:互斥锁Lock

    6:递归锁Rlock

    7:Semaphore锁

    8:同步条件event

    9:队列

    1:进程和线程的说明

    进程一般由程序、数据集、进程控制块三部分组成。
    	我们编写的程序用来描述进程要完成哪些功能以及如何完成;
    	数据集则是程序在执行过程中所需要使用的资源;
    	进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
    
    
    线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。
    

    进程和线程的大白话

    进程:本质上就是程序运行的实例 
    
    进程和进程之前的数据是不能共享的。
    线程和线程之间可以共享。
    
    比如说你开一个qq,qq就是qq程序执行的实例,是进程。而qq里面的功能就是各个线程。

    2.线程的两种调用方式

    threading 模块建立在thread 模块之上。thread模块以低级、原始的方式来处理和控制线程,而threading 模块通过对thread进行二次封装,

    提供了更方便的api来处理线程。

    直接调用:

    import threading
    import time
     
    def sayhi(num): #定义每个线程要运行的函数
     
        print("running on number:%s" %num)
     
        time.sleep(3)
     
    if __name__ == '__main__':
     
        t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例
        t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例
     
        t1.start() #启动线程
        t2.start() #启动另一个线程
     
        print(t1.getName()) #获取线程名
        print(t2.getName())
    View Code

    继承式调用:(ps没多大用,一般直接调用就好)

    import threading
    import time
    
    
    class MyThread(threading.Thread):
        def __init__(self,num):
            threading.Thread.__init__(self)
            self.num = num
    
        def run(self):#定义每个线程要运行的函数
    
            print("running on number:%s" %self.num)
    
            time.sleep(3)
    
    if __name__ == '__main__':
    
        t1 = MyThread(1)
        t2 = MyThread(2)
        t1.start()
        t2.start()
        
        print("ending......")
    View Code

    3.threading.thread的实例方法

     join方法:

    当线程对象采用join的时候,必须先执行完当前的子线程,主线程才可以工作;而子线程之间没有这个关系。

    join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

     1 import threading
     2 import time
     3 
     4 def music():
     5     print("begin to listen %s"%time.ctime())
     6     time.sleep(5)
     7     print("end to listen %s" % time.ctime())
     8 
     9 def game():
    10     print("begin to game %s"%time.ctime())
    11     time.sleep(3)
    12     print("end to game %s" % time.ctime())
    13 
    14 
    15 if __name__ == "__main__":
    16     t1 = threading.Thread(target=music) ##开启了一个线程
    17 
    18 
    19     t2 = threading.Thread(target=game) ##开启第二个线程
    20     t2.start()
    21     t1.start()
    22     t1.join()   ##t1不执行完,主线程不能走
    23     t2.join()   ##
    24     print("end.......")
    25 
    26 ##输出   bengin game 和 listen同时打印出来,过了三秒end game再打印出来,等再过2秒打印出end listen 和 end....
    27 # begin to game Sat Mar 17 22:20:37 2018
    28 # begin to listen Sat Mar 17 22:20:37 2018
    29 # end to game Sat Mar 17 22:20:40 2018
    30 # end to listen Sat Mar 17 22:20:42 2018
    31 # end.......
    View Code

    setDaemon 守护线程

     1 import threading
     2 import time
     3 
     4 def f1(i):
     5     time.sleep(1)
     6     print(i)
     7 
     8 if __name__ == '__main__':
     9     for i in range(5):
    10         t = threading.Thread(target=f1, args=(i,))
    11         t.setDaemon(True)  ##守护主线程,。只要主线程结束,就陪着一起退出
    12         t.start()
    13 
    14     print('start')      # 主线程不等待子线程
    15 
    16 ##输出
    17 #start
    View Code
    setDaemon(True):
             将线程声明为守护线程,必须在start() 方法调用之前设置, 如果不设置为守护线程程序会被无限挂起。这个方法基本和join是相反的。
             当我们 在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成
             想退出时,会检验子线程是否完成。如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程
             完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦。

    其它方法

    除此之外,自己还可以为线程自定义名字,通过 t = threading.Thread(target=f1, args=(i,), name='mythread{}'.format(i)) 中的name参数,除此之外,Thread还有一下一些方法

    # run():  线程被cpu调度后自动执行线程对象的run方法
    t.start()	:启动线程活动。
    t.getName() : 获取线程的名称
    t.setName() : 设置线程的名称 
    t.name : 获取或设置线程的名称
    t.is_alive() : 判断线程是否为激活状态
    t.isAlive() :判断线程是否为激活状态
    t.isDaemon() : 判断是否为守护线程
    
    threading模块提供的一些方法:
    # threading.currentThread(): 返回当前的线程变量。
    # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
    # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果

    4.python的GIL

    In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)

    上面的核心意思就是,无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行

    GIL是存在于pythonC解释器中,你要想在python中使用多线程并行 那就别想了。java中是不存在这个锁的,但是用户需要繁琐的加锁解锁,保障线程的安全。

    任务的区分:

    对于IO密集型的任务,python的多线程是有意义的
    对于计算密集型的任务;python的多线程不推荐,可以采用多进程+协程
    

    并发&并行

    并发:是指系统具有处理多个任务(动作)的能力 ;通俗的说就是cpu能够进行切换任务,就说具有了并发。
    并行:是值系统具有同时处理多个任务(动作)的能力
    
    并行是并发的一个子集 

    同步 & 异步

    现象:如上个博文所说的socket,当你等待用户或服务端的数据recv 时,就一直阻塞在那里。这叫同步
    同步:当进程执行到一个IO(等待外部数据的时候),等 ---就是同步
    异步:当进程执行到一个IO(等待外部数据的时候),不等 ---就是异步

    5.同步锁(也叫互斥锁)

    ###开100个线程进行 累加或累减 
    import threading
    import time
    def sub():
        global num
        temp = num
        time.sleep(0.0001)
        num = temp -1
        # num -= 1
    
    num = 100
    l = []
    for i in range(100):
        t = threading.Thread(target=sub)
        t.start()
        l.append(t)
    
    for i in l :
        i.join()
    
    print(num)
    
    # 输出:  我想要的是0  怎么给我输出了78了?
    # 78

    分析:加的time.sleep()的区别就是cpu遇见了io阻塞,马上就进行切换。

    大家都处理一个数据,而这个数据在进行切换的时候,数据还没执行完就进行切换,导致了同一时刻,不同的线程拿到了同一个数据。

    解决办法:加同步锁

    import threading
    import time
    def sub():
        global num
        lock.acquire()  ##将下面的代码锁起来,在我锁的过程谁都不能对下面的数据进行操作
        temp = num
        time.sleep(1) ##1秒意味着 100秒才可以出结果
        num = temp -1
        lock.release()  ##释放锁
        # num -= 1
    
    num = 100
    l = []
    lock = threading.Lock()  ###创建了一把锁
    for i in range(100):
        t = threading.Thread(target=sub)
        t.start()
        l.append(t)
    
    for i in l :
        i.join()
    
    print(num)
    
    # 输出:  
    # 0
    View Code
    def sub():
        global num
        print('ok')
        time.sleep(1)
        print('ok2')
        lock.acquire()  ##将下面的代码锁起来,在我锁的过程谁都不能对下面的数据进行操作
        temp = num
        time.sleep(0.01) ##1秒意味着 100秒才可以出结果
        num = temp -1
        lock.release()  ##释放锁
    
    
    ##我串行的部分就是 lock的三行部分 其他的还是 并行的,
    

    6.死锁锁、递归锁

    同步锁的缺点:

    在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:

    死锁实例:

     1 import  threading
     2 import  time
     3 
     4 class MyThread(threading.Thread):
     5     def actionA(self):
     6         A.acquire()
     7         print(self.name,"GotA",time.ctime())  ###self 是一个线程对象,打印出线程名字
     8         time.sleep(2)
     9 
    10         B.acquire()
    11         print(self.name, "GotB", time.ctime())
    12         time.sleep(2)
    13         B.release()      ##释放B锁
    14         A.release()     ##释放A锁
    15 
    16 
    17     def actionB(self):
    18         B.acquire()
    19         print(self.name, "GotB", time.ctime())  ###self 是一个线程对象,打印出线程名字
    20         time.sleep(1)
    21 
    22         A.acquire()
    23         print(self.name, "GotA", time.ctime())
    24         A.release()  ##释放A锁
    25         B.release()  ##释放B锁
    26 
    27     def run(self):
    28         self.actionA()
    29         self.actionB()
    30 
    31 if __name__ == '__main__':
    32 
    33     ##创建两把锁
    34     A = threading.Lock()
    35     B = threading.Lock()
    36     L = []
    37     for i in range(5):
    38         t = MyThread()
    39         t.start()
    40         L.append(t)
    41 
    42     for i in L:
    43         i.join()
    44 
    45     print('ending.........')
    46 
    47 
    48 ##5个线程一起走都要获取到lock a ,线程1拿到了a 其他四个都要等待,线程1 释放 a锁才能进来,
    49 #线程1
    50 
    51 # 输出:
    52 # Thread-1 GotA Sun Mar 18 12:07:12 2018
    53 # Thread-1 GotB Sun Mar 18 12:07:14 2018
    54 # Thread-1 GotB Sun Mar 18 12:07:16 2018
    55 # Thread-2 GotA Sun Mar 18 12:07:16 2018
    View Code

    解决办法 递归锁(Rlock):

    由于线程是共享同一份内存的,所以如果操作同一份数据,很容易造成冲突,这时候就可以为线程加上一个锁了,这里我们使用Rlock,而不使用Lock,因为Lock如果多次获取锁的时候会出错,而RLock允许在同一线程中被多次acquire,但是需要用n次的release才能真正释放所占用的琐,一个线程获取了锁在释放之前,其他线程只有等待。

    import  threading
    import  time
    
    class MyThread(threading.Thread):
        def actionA(self):
            rlock.acquire()
            print(self.name,"GotA",time.ctime())  ###self 是一个线程对象,打印出线程名字
            time.sleep(2)
    
            rlock.acquire()
            print(self.name, "GotB", time.ctime())
            time.sleep(2)
            rlock.release()      ##释放B锁
            rlock.release()     ##释放A锁
    
    
        def actionB(self):
            rlock.acquire()
            print(self.name, "GotB", time.ctime())  ###self 是一个线程对象,打印出线程名字
            time.sleep(1)
    
            rlock.acquire()
            print(self.name, "GotA", time.ctime())
            rlock.release()  ##释放A锁
            rlock.release()  ##释放B锁
    
        def run(self):
            self.actionA()
            self.actionB()
    
    if __name__ == '__main__':
    
        ##创建两把锁
        # A = threading.Lock()
        # B = threading.Lock()
        L = []
        ##创建递归锁
        rlock = threading.RLock()
        for i in range(5):
            t = MyThread()
            t.start()
            L.append(t)
    
        for i in L:
            i.join()
    
        print('ending.........')
    
    
    ##5个线程一起走都要获取到lock a ,线程1拿到了a 其他四个都要等待,线程1 释放 a锁才能进来,
    #线程1
    
    # 输出:
    # Thread-1 GotA Sun Mar 18 12:17:52 2018
    # Thread-1 GotB Sun Mar 18 12:17:54 2018
    # Thread-1 GotB Sun Mar 18 12:17:56 2018
    # Thread-1 GotA Sun Mar 18 12:17:57 2018
    # Thread-3 GotA Sun Mar 18 12:17:57 2018
    # Thread-3 GotB Sun Mar 18 12:17:59 2018
    # Thread-3 GotB Sun Mar 18 12:18:01 2018
    # Thread-3 GotA Sun Mar 18 12:18:02 2018
    # Thread-5 GotA Sun Mar 18 12:18:02 2018
    # Thread-5 GotB Sun Mar 18 12:18:04 2018
    # Thread-2 GotA Sun Mar 18 12:18:06 2018
    # Thread-2 GotB Sun Mar 18 12:18:08 2018
    # Thread-2 GotB Sun Mar 18 12:18:10 2018
    # Thread-2 GotA Sun Mar 18 12:18:11 2018
    # Thread-5 GotB Sun Mar 18 12:18:11 2018
    # Thread-5 GotA Sun Mar 18 12:18:12 2018
    # Thread-4 GotA Sun Mar 18 12:18:12 2018
    # Thread-4 GotB Sun Mar 18 12:18:14 2018
    # Thread-4 GotB Sun Mar 18 12:18:16 2018
    # Thread-4 GotA Sun Mar 18 12:18:17 2018
    # ending.........
    View Code

    递归锁的,内部就是维护 了一个计算器默认是0,当有人用rlock 就+1,直到rlock = 0 才能继续,让线程竞争锁,谁抢到谁执行。

    7:Semaphore锁

    Semaphore锁也是锁的一种,类似于停车场 ,停车场一次可以停3辆车,当第三辆车来了之后,只能等待前面三辆车离开大于等于1辆 才能进入。

    实例:

    import  threading
    import time
    
    
    
    
    class MyThread(threading.Thread):
        def run(self):
            if more.acquire():  ##加锁 当停车场小于三的时候,车进入
                time.sleep(3)
                print(self.name)
                more.release()  ## 解锁,车离开 允许后面的车进来
    
    more = threading.Semaphore(3)  ##默认停车场三辆
    
    l = []
    for i in range(23):
        t = MyThread()
        t.start()
        l.append(t)
    
    for i in l:
        i.join()
    
    print('end........')
    
    
    # 输出: 没三秒输出三个线程
    # Thread-2
    # Thread-1
    # Thread-3
    
    # Thread-4
    # Thread-5
    # Thread-6
    View Code

    8:同步条件event

    在之前不管是加锁还是其他的,线程都是处于竞争的,谁抢到cpu就执行谁。而达不到一个相互协调工作的情况。

    event的原理很简单:就是线程之间共同围绕着一个标志位,当线程A没有set标志位的时候,线程B阻塞住。当线程A set标志位的时候,让B线程执行。

    达到一个同步的效果:

    就三个方法记住:

    # a client thread can wait for the flag to be set
    event.wait()

    # a server thread can set or reset it
    event.set()
    event.clear()

    import threading,time
    class Boss(threading.Thread):
        def run(self):
            print("BOSS:今晚大家都要加班到22:00。")
            print(event.isSet())
            event.set()
            time.sleep(5)
            print("BOSS:<22:00>可以下班了。")
            print(event.isSet())
            event.set()
    class Worker(threading.Thread):
        def run(self):
            event.wait()   ##boss没set 就阻塞住,一但boss set开始执行
            print("Worker:哎……命苦啊!")
            time.sleep(1)
            event.clear()
            event.wait()
            print("Worker:OhYeah!")
    if __name__=="__main__":
        event=threading.Event()
        threads=[]
        for i in range(5):   ##创建5个work
            threads.append(Worker())
        threads.append(Boss()) ##创建一个Boss
        for t in threads:
            t.start()
        for t in threads:
            t.join()
            
            
    # 输出:
    # BOSS:今晚大家都要加班到22:00。
    # False
    # Worker:哎……命苦啊!
    # Worker:哎……命苦啊!
    # Worker:哎……命苦啊!
    # Worker:哎……命苦啊!
    # Worker:哎……命苦啊!
    # BOSS:<22:00>可以下班了。
    # False
    # Worker:OhYeah!
    # Worker:OhYeah!
    # Worker:OhYeah!
    # Worker:OhYeah!
    # Worker:OhYeah!
    

    9.队列Queue

    队列是一种数据结构,不懂什么是数据结构?

    数据结构就是帮你存储数据的一种格式,比如说列表 是按一个个索引值去存储数据,而字典是通过哈希给你存储数据。

    为什么要有队列?

    之所以会出现队列是因为遇见了多线程,它保证了数据的安全。

    创建队列对象

    import  queue
    
    q = queue.Queue()
    q = queue.Queue(5) ##就表示创建了一个5个格子的队列

    创建一个队列对象就类似创建了这样的一个格子,接下来我们就要往里面添值。

     添值

    q.put(12)
    q.put('hi')
    q.put([1,3,4])
    

    获取值

    while 1 :
        data = q.get()
        print(data)
    

    输出:

    12
    hi
    [1, 3, 4]
    

    完整代码

    import  queue
    
    q = queue.Queue()
    
    q.put(12)
    q.put('hi')
    q.put([1,3,4])
    
    while 1 :
        data = q.get()
        print(data)
    View Code

    我们说创建了一个格子,那么开始是怎么放置值的?

    先进先出队列  queue.Queue()
    后进先出队列  queue.LifoQueue()
    按优先级队列  queue.PriorityQueue()
    
     1 import  queue
     2 
     3 # q = queue.Queue()
     4 q = queue.LifoQueue()
     5 # q = queue.PriorityQueue()
     6 
     7 q.put(12)
     8 q.put('hi')
     9 q.put([1,3,4])
    10 
    11 while 1 :
    12     data = q.get()
    13     print(data)
    14 
    15 # 输出:
    16 # [1, 3, 4]
    17 # hi
    18 # 12
    后进先出

    数据越小优先级越高

     1 import  queue
     2 
     3 q = queue.PriorityQueue()
     4 
     5 q.put([2,'hi'])
     6 q.put([4,34])
     7 q.put([6,[1,3,4]])
     8 
     9 while 1 :
    10     data = q.get()
    11     # print(data)
    12     print(data[1])
    13 
    14 # 输出:  默认输出的是一个列表
    15 # [2, 'hi']
    16 # [4, 34]
    17 # [6, [1, 3, 4]]
    18 
    19 # hi
    20 # 34
    21 # [1, 3, 4]
    优先级队列

    其他的一些方法

    q.qsize() 返回队列的大小
    q.empty() 如果队列为空,返回True,反之False
    q.full() 如果队列满了,返回True,反之False
    q.full 与 maxsize 大小对应
    q.get([block[, timeout]]) 获取队列,timeout等待时间
    q.get_nowait() 相当q.get(False)  ##不等待程序往队列放值,直接报错
    非阻塞 q.put(item) 写入队列,timeout等待时间
    q.put_nowait(item) 相当q.put(item, False)  ##当队列满了,阻塞住程序,直到队列get出去,此方法表示不等待,直接报错
    q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
    q.join() 实际上意味着等到队列为空,再执行别的操作


      

     

  • 相关阅读:
    element-ui的气泡确认框
    ES6 检测数组中是否存在满足某些条件的元素实现方式
    P6788 「EZEC-3」四月樱花
    Codeforces Global Round 10(CF1392)
    Ynoi2019模拟赛
    谷粒学院项目分享(源码+资料+课件)全部齐全
    安装最新版NUXT
    LibreOJ #6284
    LibreOJ #6283
    LibreOJ #6282
  • 原文地址:https://www.cnblogs.com/hero799/p/8590927.html
Copyright © 2011-2022 走看看