• 4.4 线程


    线程

    相关概念

     进程线程关系图

    threading 模块

    线程创建

      方法形式创建线程

    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello' %name)
    
    if __name__ == '__main__':
        t=Thread(target=sayhi,args=('egon',))
        t.start()
        print('主线程')
    View Code

       类形式创建线程(必须内含有 run方法 以及继承 Thread)

    from threading import Thread
    import time
    class Sayhi(Thread):
        def __init__(self,name):
            super().__init__()
            self.name=name
        def run(self):
            time.sleep(2)
            print('%s say hello' % self.name)
    
    
    if __name__ == '__main__':
        t = Sayhi('egon')
        t.start()
        print('主线程')
    View Code

    相关方法

    Thread实例对象的方法
      # isAlive(): 返回线程是否活动的。
      # getName(): 返回线程名。
      # setName(): 设置线程名。
    
    threading模块提供的一些方法:
      # threading.currentThread(): 返回当前的线程变量。
      # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
      # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

       join 方法实例

    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello' %name)
    
    if __name__ == '__main__':
        t=Thread(target=sayhi,args=('egon',))
        t.start()
        t.join()
        print('主线程')
        print(t.is_alive())
        '''
        egon say hello
        主线程
        False
        '''
    View Code

      守护线程实例

    from threading import Thread
    import time
    def sayhi(name):
        time.sleep(2)
        print('%s say hello' %name)
    
    if __name__ == '__main__':
        t=Thread(target=sayhi,args=('egon',))
        t.setDaemon(True) #必须在t.start()之前设置
        t.start()
    
        print('主线程')
        print(t.is_alive())
        '''
        主线程
        True
        '''
    View Code
    from threading import Thread
    import time
    def foo():
        print(123)
        time.sleep(1)
        print("end123")
    
    def bar():
        print(456)
        time.sleep(3)
        print("end456")
    
    
    t1=Thread(target=foo)
    t2=Thread(target=bar)
    
    t1.daemon=True
    t1.start()
    t2.start()
    print("main-------")
    View Code

    同步锁  

      用法和场景同 进程 

    import threading
    R=threading.Lock()
    R.acquire()
    '''
    对公共数据的操作
    '''
    R.release()

    死锁/重入锁

      死锁实例

    from threading import Lock as Lock
    import time
    mutexA=Lock()
    mutexA.acquire()
    mutexA.acquire()
    print(123)
    mutexA.release()
    mutexA.release()

      科学家吃面死锁问题

    import time
    from threading import Thread,Lock
    noodle_lock = Lock()
    fork_lock = Lock()
    def eat1(name):
        noodle_lock.acquire()
        print('%s 抢到了面条'%name)
        fork_lock.acquire()
        print('%s 抢到了叉子'%name)
        print('%s 吃面'%name)
        fork_lock.release()
        noodle_lock.release()
    
    def eat2(name):
        fork_lock.acquire()
        print('%s 抢到了叉子' % name)
        time.sleep(1)
        noodle_lock.acquire()
        print('%s 抢到了面条' % name)
        print('%s 吃面' % name)
        noodle_lock.release()
        fork_lock.release()
    
    for name in ['哪吒','egon','yuan']:
        t1 = Thread(target=eat1,args=(name,))
        t2 = Thread(target=eat2,args=(name,))
        t1.start()
        t2.start()
    View Code

      解决方式:重入锁

    import time
    from threading import Thread,RLock
    fork_lock = noodle_lock = RLock()
    def eat1(name):
        noodle_lock.acquire()
        print('%s 抢到了面条'%name)
        fork_lock.acquire()
        print('%s 抢到了叉子'%name)
        print('%s 吃面'%name)
        fork_lock.release()
        noodle_lock.release()
    
    def eat2(name):
        fork_lock.acquire()
        print('%s 抢到了叉子' % name)
        time.sleep(1)
        noodle_lock.acquire()
        print('%s 抢到了面条' % name)
        print('%s 吃面' % name)
        noodle_lock.release()
        fork_lock.release()
    
    for name in ['哪吒','egon','yuan']:
        t1 = Thread(target=eat1,args=(name,))
        t2 = Thread(target=eat2,args=(name,))
        t1.start()
        t2.start()
    View Code

    信号量 Semaphore 

       信号量 Semaphore 举例

    from threading import Thread,Semaphore,currentThread
    import time,random
    sm = Semaphore(5) #运行的时候有5个人
    def task():
        sm.acquire()
        print('33[42m %s上厕所'%currentThread().getName())
        time.sleep(random.randint(1,3))
        print('33[31m %s上完厕所走了'%currentThread().getName())
        sm.release()
    if __name__ == '__main__':
        for i in range(20):  #开了10个线程 ,这20人都要上厕所
            t = Thread(target=task)
            t.start()
    View Code

     

    Event 事件

    线程的一个关键特性是每个线程都是独立运行且状态不可预测。

    如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。

    为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。

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

    一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。

    如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

    简单来说:

      当存在多个线程之间有这状态依附时,使用 Event

      Event 对象的 初始 状态值 为 False 当 isSet 判断状态时就一直 wait 阻塞

      当达到需求的状态时,通过 set 将状态置 True (或者通过 clear 恢复成 False)

      为 True 时才可以正确下面的代码执行

      

    from threading import Event
    Event.isSet() #返回event的状态值
    Event.wait() #如果 event.isSet()==False将阻塞线程;
    Event.set() #设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
    Event.clear() #恢复

      链接数据库实例 

    # 首先定义两个函数,一个是连接数据库
    # 一个是检测数据库
    from threading import Thread, Event, currentThread
    import time
    
    e = Event()
    
    
    def conn_mysql():
        '''链接数据库'''
        count = 1
        while not e.is_set():  # 当没有检测到时候 is_set 为 False
            if count > 3:  # 如果尝试次数大于3,就主动抛异常
                raise ConnectionError('尝试链接的次数过多')
            print('33[45m%s 第%s次尝试' % (currentThread(), count))
            e.wait(timeout=1)  # 等待检测(里面的参数是超时1秒)
            count += 1
        print('33[44m%s 开始链接...' % (currentThread().getName()))
    
    
    def check_mysql():
        '''检测数据库'''
        print('33[42m%s 检测mysql...' % (currentThread().getName()))
        time.sleep(5)
        e.set()     # 检测成功后设置为 True
    
    
    if __name__ == '__main__':
        for i in range(3):  # 三个去链接
            t = Thread(target=conn_mysql)
            t.start()
        t = Thread(target=check_mysql)
        t.start()
    View Code

       红绿灯例子

    from  threading import Thread,Event,currentThread
    import time
    e = Event()
    def traffic_lights():
        '''红绿灯'''
        time.sleep(5)
        e.set()
    def car():
        ''''''
        print('33[42m %s 等绿灯33[0m'%currentThread().getName())
        e.wait()
        print('33[44m %s 车开始通行' % currentThread().getName())
    if __name__ == '__main__':
        for i in range(10):
            t = Thread(target=car)  #10辆车
            t.start()
        traffic_thread = Thread(target=traffic_lights)  #一个红绿灯
        traffic_thread.start()
    View Code

    定时器

      指定n秒后执行某操作

    from threading import Timer
    def func(n):
        print('hello,world',n)
    t = Timer(3,func,args=(123,))  #等待三秒后执行func函数,因为func函数有参数,那就再传一个参数进去
    t.start()

    GIL 全局解释器锁

    概念

      在Cpython解释器中,因为有GIL锁的存在同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。 

      同一时刻同一进程中只有一个线程被执行

    线程池

    创建方法

    from  concurrent.futures import ThreadPoolExecutor
    from threading import currentThread
    import os,time,random
    def task(n):
        print('%s:%s is running'%(currentThread().getName(),os.getpid()))  #看到的pid都是一样的,因为线程是共享了一个进程
        time.sleep(random.randint(1,3))  #I/O密集型的,,一般用线程,用了进程耗时长
        return n**2
    if __name__ == '__main__':
        start = time.time()
        p = ThreadPoolExecutor() #线程池 #如果不给定值,默认cup*5
        l = []
        for i in range(10):  #10个任务 # 线程池效率高了
            obj  = p.submit(task,i)  #相当于apply_async异步方法
            l.append(obj)
        p.shutdown()  #默认有个参数wite=True (相当于close和join)
        print('='*30)
        print([obj.result() for obj in l])
        print(time.time() - start)  #3.001171827316284
    
    """
    ThreadPoolExecutor-0_0:12816 is running
    ThreadPoolExecutor-0_1:12816 is running
    ThreadPoolExecutor-0_2:12816 is running
    ThreadPoolExecutor-0_3:12816 is running
    ThreadPoolExecutor-0_4:12816 is running
    ThreadPoolExecutor-0_5:12816 is running
    ThreadPoolExecutor-0_6:12816 is running
    ThreadPoolExecutor-0_7:12816 is running
    ThreadPoolExecutor-0_8:12816 is running
    ThreadPoolExecutor-0_9:12816 is running
    ==============================
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    3.0191218852996826
    
    """

    线程池实例

    from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
    import requests
    import time,os
    def get_page(url):
        print('<%s> is getting [%s]'%(os.getpid(),url))
        response = requests.get(url)
        if response.status_code==200:  
            return {'url':url,'text':response.text}
    def parse_page(res):
        res = res.result() # 需要用 result 拿到对象结果
        print('<%s> is getting [%s]'%(os.getpid(),res['url']))
        with open('db.txt','a') as f:
            parse_res = 'url:%s size:%s
    '%(res['url'],len(res['text']))
            f.write(parse_res)
    if __name__ == '__main__':
        # p = ThreadPoolExecutor()
        p = ProcessPoolExecutor()
        l = [
            'http://www.baidu.com',
            'http://www.baidu.com',
            'http://www.baidu.com',
            'http://www.baidu.com',
        ]
        for url in l:
            res = p.submit(get_page,url).add_done_callback(parse_page) #这里的回调函数拿到的是一个对象。
        #  需要先把返回的res得到一个结果。即在前面加上一个res.result() ,谁好了谁去掉用回调函数
        #  回调函数也是一种编程思想。不仅开线程池用,开线程池也用
        p.shutdown()  #相当于进程池里的close和join
        print('',os.getpid())

    map 应用实例

        map 也可以回调函数,所以功能上可以替换

    # 我们的那个p.submit(task,i)和map函数的原理类似。我们就
    # 可以用map函数去代替。更减缩了代码
    from  concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
    import os,time,random
    def task(n):
        print('[%s] is running'%os.getpid())
        time.sleep(random.randint(1,3))  #I/O密集型的,,一般用线程,用了进程耗时长
        return n**2
    if __name__ == '__main__':
        p = ProcessPoolExecutor()
        obj = p.map(task,range(10))
        p.shutdown()  #相当于close和join方法
        print('='*30)
        print(obj)  #返回的是一个迭代器
        print(list(obj))
    
    # map函数应用
  • 相关阅读:
    JDK源码分析 – HashMap
    牛哄哄的celery
    redis数据库基础篇
    RPC的入门应用
    Python的常用模块
    消息队列之真知灼见
    面向对象编程(2)
    python3的C3算法
    面向对象编程(1)
    CRM项目之stark组件(2)
  • 原文地址:https://www.cnblogs.com/shijieli/p/10340390.html
Copyright © 2020-2023  润新知