• 第十篇:线程


    本篇内容

    1. 开启线程的方式
    2. 线程pid
    3. 线程其他方法
    4. 守护线程
    5. GIL
    6. 互斥锁
    7. Event
    8. 线程池

    一、 开启线程的方式

    开启线程的方式分为两种:

    (1)利用模块开启线程:

    #!/usr/binl/env python
    #encoding: utf-8
    #author: YangLei
    
    from threading import Thread
    
    def f1():
        print("f1 is running")
    
    if __name__ == '__main__':
        t = Thread(target=f1,)
        t.start()
        print("主")
    

     (2)利用类开启线程:

    #!/usr/binl/env python
    #encoding: utf-8
    #author: YangLei
    
    from threading import Thread
    
    class CustomThread(Thread):
        def __init__(self, name):
            super().__init__()
            self.name = name
    
        def run(self):
            print('%s is running' % self.name)
    
    if __name__ == '__main__':
        t = CustomThread("yanglei")
        t.start()
        print("主")
    

     总结:由执行过程可以看出,线程的开启速度比进程开启的速度快。

    二、线程pid

    同一个进程中,开启的线程pid相同。

    #!/usr/binl/env python
    #encoding: utf-8
    #author: YangLei
    
    from threading import Thread
    import os
    
    def f1():
        print("%s is running" % os.getpid())
    
    if __name__ == '__main__':
        t1 = Thread(target=f1,)
        t2 = Thread(target=f1,)
        t1.start()
        t2.start()
        print("主", os.getpid())
    

    由此可见,同一个进程,线程pid相同,那么我们是不是可以大胆猜测,同一个进程中的线程资源是共享的,下面一段代码就来验证我们的猜想。

    #!/usr/binl/env python
    #encoding: utf-8
    #author: YangLei
    
    from threading import Thread
    
    input_info_list = []
    format_list = []
    
    def f1():
        while True:
            input_info = input(">>>: ").strip()
            input_info_list.append(input_info)
    
    def format():
        while True:
            if input_info_list:
                data = input_info_list.pop()
                format_list.append(data.upper())
    
    def save():
        while True:
            if format_list:
                data = format_list.pop()
                with open("save_data.txt", "a") as f:
                    f.write("%s
    " % data)
    
    if __name__ == '__main__':
        t1 = Thread(target=f1)
        t2 = Thread(target=format)
        t3 = Thread(target=save)
        t1.start()
        t2.start()
        t3.start()
    

    三、线程其他方法

    current_thread:

    该方法的的结果可以让我们得到一个MainThread对象,而getName函数可以使我们获得该线程的线程名。

    #!/usr/binl/env python
    #encoding: utf-8
    #author: YangLei
    
    from threading import Thread, current_thread
    import time
    
    def f1():
        print('%s is running' % current_thread().getName())
        time.sleep(2)
    
    if __name__ == '__main__':
        t1 = Thread(target=f1)
        t2 = Thread(target=f1)
        t3 = Thread(target=f1)
        t1.start()
        t2.start()
        t3.start()
        print(current_thread())
    

    四、守护线程

    守护线程跟守护进程不同,因线程开启的速度快,所以线程一定会被开启,且如果主进程运行的时间大于守护线程的时间,该守护线程也可以正常运行结束。

    例如:

    #!/usr/binl/env python
    #encoding: utf-8
    #author: YangLei
    
    from threading import Thread
    import time
    
    def f1():
        print("f1")
        time.sleep(3)
        print("f1 is done")
    
    def f2():
        print("f2")
        time.sleep(10)
        print("f2 is done")
    
    if __name__ == '__main__':
        t1 = Thread(target=f1)
        t2 = Thread(target=f2)
        t1.daemon=True
        t1.start()
        t2.start()
        print('主')
    

    五、GIL

    1.定义:

    Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
    在多线程环境中,Python 虚拟机按以下方式执行:
    (1) 设置GIL
    (2)切换到一个线程去运行
    (3)运行:
      a. 指定数量的字节码指令,或者
      b. 线程主动让出控制(可以调用time.sleep(0))
    (4)把线程设置为睡眠状态
    (5)解锁GIL
    (6)再次重复以上所有步骤

    在调用外部代码(如C/C++扩展函数)的时候,GIL 将会被锁定,直到这个函数结束为止(由于在这期间没有Python 的字节码被运行,所以不会做线程切换)。

    2.设计理念:

    GIL的设计简化了CPython的实现,使得对象模型,包括关键的内建类型如字典,都是隐含可以并发访问的。锁住全局解释器使得比较容易的实现对多线程的支持,但也损失了多处理器主机的并行计算能力。
    但是,不论标准的,还是第三方的扩展模块,都被设计成在进行密集计算任务是,释放GIL。
    还有,就是在做I/O操作时,GIL总是会被释放。对所有面向I/O 的(会调用内建的操作系统C 代码的)程序来说,GIL 会在这个I/O 调用之前被释放,以允许其它的线程在这个线程等待I/O 的时候运行。如果是纯计算的程序,没有 I/O 操作,解释器会每隔 100 次操作就释放这把锁,让别的线程有机会执行(这个次数可以通过 sys.setcheckinterval 来调整)如果某线程并未使用很多I/O 操作,它会在自己的时间片内一直占用处理器(和GIL)。也就是说,I/O 密集型的Python 程序比计算密集型的程序更能充分利用多线程环境的好处。

     

    六、互斥锁

    1.定义:

    线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

    #!/usr/binl/env python
    #encoding: utf-8
    #author: YangLei
    
    from threading import Thread, Lock
    import time
    
    count = 50
    
    def work():
        global count
        mutex.acquire()
        tmp = count
        time.sleep(0.1)
        count = tmp - 1
        mutex.release()
    
    if __name__ == '__main__':
        mutex = Lock()
        l = []
        start = time.time()
        for i in range(50):
            t = Thread(target=work)
            l.append(t)
            t.start()
        for t in l:
            t.join()
        print("run time:%s value:%s" % (time.time()-start, count))
    

     2.互斥锁与join的区别:

    根据刚才的例子,引发了我们的猜想,就刚才的例子来说,互斥锁跟join基本上并无太大的区别,实则不然。因这段代码特殊,写的也比较短,这几行全是运算操作,如果在运算操作之前还有其他操作,那么它俩的区别就会体现出来了。互斥锁会明显比join运行的速度快。

    (1)互斥锁:

    #!/usr/binl/env python
    #encoding: utf-8
    #author: YangLei
    
    from threading import Thread, Lock
    import time
    
    count = 50
    
    def work():
        time.sleep(0.05)
        global count
        mutex.acquire()
        tmp = count
        time.sleep(0.1)
        count = tmp - 1
        mutex.release()
    
    if __name__ == '__main__':
        mutex = Lock()
        l = []
        start = time.time()
        for i in range(50):
            t = Thread(target=work)
            l.append(t)
            t.start()
        for t in l:
            t.join()
        print("run time:%s value:%s" % (time.time()-start, count))
    

    (2)join:

    #!/usr/binl/env python
    #encoding: utf-8
    #author: YangLei
    
    from threading import Thread
    import time
    
    count = 50
    
    def work():
        time.sleep(0.05)
        global count
        tmp = count
        time.sleep(0.1)
        count = tmp - 1
    
    
    if __name__ == '__main__':
        start = time.time()
        for i in range(50):
            t = Thread(target=work)
            t.start()
            t.join()
    
        print('run time:%s value:%s' %(time.time()-start, count))
    

    七、Event

    Python提供了Event对象用于线程间通信,它是由线程设置的信号标志,如果信号标志位为假,则线程等待直到信号被其他线程设置成真。这一点似乎和windows的event正好相反。 Event对象实现了简单的线程通信机制,它提供了设置信号,清除信号,等待等用于实现线程间的通信。

    (1)设置信号

    使用Event的set()方法可以设置Event对象内部的信号标志为真。Event对象提供了isSet()方法来判断其内部信号标志的状态,当使用event对象的set()方法后,isSet()方法返回真。

    (2)清除信号

    使用Event对象的clear()方法可以清除Event对象内部的信号标志,即将其设为假,当使用Event的clear方法后,isSet()方法返回假。

    (3)等待

    Event对象wait的方法只有在内部信号为真的时候才会很快的执行并完成返回。当Event对象的内部信号标志位假时,则wait方法一直等待到其为真时才返回。

    #!/usr/binl/env python
    #encoding: utf-8
    #author: YangLei
    
    from threading import Thread,current_thread,Event
    import time
    
    event = Event()
    
    def conn_mysql():
        count = 1
        while not event.is_set():
            if count > 3:
                raise ConnectionError('链接失败')
            print('%s 等待第%s次链接mysql' % (current_thread().getName(), count))
            event.wait(0.5)
            count += 1
        print('%s 链接ok' % current_thread().getName())
    
    def check_mysql():
        print('%s 正在检查mysql状态' % current_thread().getName())
        time.sleep(1)
        event.set()
    
    if __name__ == '__main__':
        t1 = Thread(target=conn_mysql)
        t2 = Thread(target=conn_mysql)
        check = Thread(target=check_mysql)
        t1.start()
        t2.start()
        check.start()
    

    八、线程池

    用ThreadPoolExecutor与用ProcessPoolExecutor看起来没什么区别,只是改了一下签名而已。

    不难看出,不管是使用队列还是使用进/线程池,从多进程转化到多线程是十分容易的——仅仅是修改了几个签名而已。当然内部机制完全不同,只是python的封装非常好,使我们可以不用关心这些细节,这正是python优雅之处。

    #!/usr/binl/env python
    #encoding: utf-8
    #author: YangLei
    
    import requests
    import os,time,threading
    from concurrent.futures import ThreadPoolExecutor
    
    def get_page(url):
        print("<%s> get :%s" % (threading.current_thread().getName(), url))
        respone = requests.get(url)
        if respone.status_code == 200:
            return {"url": url, "text": respone.text}
    
    def parse_page(obj):
        dic = obj.result()
        print("<%s> parse :%s" % (threading.current_thread().getName(), dic["url"]))
        time.sleep(0.5)
        res = "url:%s size:%s
    " % (dic["url"], len(dic["text"]))
        with open("db.txt", "a") as f:
            f.write(res)
    
    if __name__ == '__main__':
        p = ThreadPoolExecutor(3)
        urls = [
            "http://www.baidu.com",
            "http://www.baidu.com",
            "http://www.baidu.com",
            "http://www.baidu.com",
            "http://www.baidu.com",
            "http://www.baidu.com",
            "http://www.baidu.com",
            "http://www.baidu.com",
        ]
        for url in urls:
            p.submit(get_page,url).add_done_callback(parse_page)
        p.shutdown()
        print("主进程pid:", os.getpid())
    
  • 相关阅读:
    关于web开发的一点理解
    如何通过Request.ServerVariables["HTTP_USER_AGENT"]获取客户端操作系统信息
    将图片转换为Base64
    asp.net 图片质量压缩(不改变尺寸)
    下载网路图片并保存在硬盘
    HTTP Content-type 对照表
    mssql 查询全部用户创建表 条数及占用空间大小(KB)
    幸运抽奖数据结构
    非UI线程和UI线程通信
    爬取淘宝“手机信息”
  • 原文地址:https://www.cnblogs.com/00doudou00/p/7484969.html
Copyright © 2020-2023  润新知