zoukankan      html  css  js  c++  java
  • 第十篇:线程

    本篇内容

    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())
    
  • 相关阅读:
    HDU 2116 Has the sum exceeded
    HDU 1233 还是畅通工程
    HDU 1234 开门人和关门人
    HDU 1283 最简单的计算机
    HDU 2552 三足鼎立
    HDU 1202 The calculation of GPA
    HDU 1248 寒冰王座
    HDU 1863 畅通工程
    HDU 1879 继续畅通工程
    颜色对话框CColorDialog,字体对话框CFontDialog使用实例
  • 原文地址:https://www.cnblogs.com/00doudou00/p/7484969.html
Copyright © 2011-2022 走看看