美文网首页
python多线程

python多线程

作者: 清水秋香 | 来源:发表于2020-03-21 20:26 被阅读0次
    • 并发与并行
      并发和并行是两个非常容易混淆的概念。他们都可以表示两个或多个任务一起执行,但是偏重点有点不同。并发偏重于多个任务交替执行,而多个任务之间有可能还是串行的。并发是逻辑上的同时发生,而并行是物理上的同时发生,然而并行的偏重点在于同时执行。
      并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进行同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。这就能好比一个人一把铁锹同时挖三个坑,每个坑都挖一下然后去挖下一个,虽然三个坑都变大,但实际上同一时间只有一个坑在被挖。
      严格意义上来说,并行的多个任务是真实的同时执行,而对于并发来说,这个过程只是交替的,一会运行任务一,一会运行任务二,系统会不停的在两者间切换,对于外部观察者来说,即使多个任务是串行并发的,也会造成多个任务并行执行的错觉。
      并行,指在同一时刻,有多条指令在多个处理器上同时执行,就好像三个人三把铁锹同时在挖三个坑,三个坑一起变大,所以无论从微视还是宏观来看,二者都是一起执行的。

    • 线程与进程
      开个qq,开了一个进程;开个迅雷,开了一个进程
      在qq的这个进程里,传输文字开一个线程。在这个软件运行的过程(在这个进程里),多个工作同时运转,完成了qq的运行,那么这“多个工作”分别有一个线程,所以一个进程管着多个线程,一个进程有且至少有一个线程

    线程处理I/O密集型任务

    import threading
    
    import time
    
    start_time = time.time()
    
    
    def foo(something):
        print(something)
        time.sleep(1)
    
    
    # 串行两秒
    # foo('xxoo')
    # foo('xoxo')
    
    # 线程1秒
    t1 = threading.Thread(target=foo, args=['xxoo'])
    t2 = threading.Thread(target=foo, args=['oxox'])
    
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    end_time = time.time()
    print(end_time - start_time)
    
    
    • 线程处理计算密集型任务
    import time
    import threading
    
    def foo():
        num = 0
        for i in range(10000000):
            num+=i
    
    
    t1 = threading.Thread(target=foo)
    t2 = threading.Thread(target=foo)
    
    
    start_time = time.time()
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    #线程 1.1275379657745361
    end_time = time.time()
    print('线程',end_time - start_time)
    #串行时间1.126770257949829
    start_time = time.time()
    foo()
    foo()
    end_time = time.time()
    print('串行',end_time - start_time)
    

    实际上,解释器被一个全局解释器锁保护着,它确保任何时候都只有一个Python线程执行。 GIL最大的问题就是Python的多线程程序并不能利用多核CPU的优势 (比如一个使用了多个线程的计算密集型程序只会在一个单CPU上面运行)。
    在讨论普通的GIL之前,有一点要强调的是GIL只会影响到那些严重依赖CPU的程序(比如计算型的)。 如果你的程序大部分只会涉及到I/O,比如网络交互,那么使用多线程就很合适, 因为它们大部分时间都在等待。实际上,你完全可以放心的创建几千个Python线程, 现代操作系统运行这么多线程没有任何压力,没啥可担心的。

    • join & setDaemon
      setDaemon(True):
      将线程生命为守护线程,必须在start()方法调用之前设置,如果不设置为守护线程程序会被无限挂起,这个方法基本和join是相反的。当我们在程序运行中,执行一个主线程,如果主线程又创建了一个子线程,主线程和子线程就兵分两路,分别运行,那么当主线程完成想退出时,会检查子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后在退出,但是有时候我们需要的是只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon方法
      join:在子线程完成运行之前,这个子线程的父线程将一致被阻塞
    import threading
    import time
    def music():
        for i in range(30):
            print('听音乐')
            time.sleep(1)
    
    def video():
        for i in range(3):
            print('看电影')
            time.sleep(1)
    t1 = threading.Thread(target=music) #生成一个线程实例
    t2 = threading.Thread(target=video)
    
    t1.setDaemon(True)
    t1.start()
    t2.start()
    t2.join()
    print("电影结束了")
    
    • 此示例展示了多个线程同时操作一个共享资源,所以造成了资源破坏,使用同步锁解决
    import time,threading
    account_balance = 500
    def option_num(num):
        global account_balance
        balance = account_balance
        time.sleep(1)
        balance = balance+num
        account_balance = balance
    
    t1 = threading.Thread(target=option_num,args=[10000])
    t2 = threading.Thread(target=option_num,args=[-300])
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(account_balance)
    
    • 同步锁
      如代码,需要操作账户余额的时候,我们给他上一把锁,同一时间只允许一个线程进行操作,等操作完毕在把锁打开,如此就避免了数据安全的问题。(线程共享全局变量)
      例一:
    import threading
    #多线程全局变量问题
    #进程系统分配资源的
    #cup调度线程没有自己独立的内存空间
    #电脑有8个核,运行线程的只有一个核
    import threading
    import time
    from threading import Lock
    #多线程同时对一个全局变量操作
    a = 100
    
    def func1():
        global a
        lock.acquire()
        for i in range(10000000):
            a += 1
        lock.release()
        print('线程1修改完',a)
    
    def func2():
        global a
        lock.acquire()
        for i in range(10000000):
            a += 1
        print('线程2修改完',a)
        lock.release()
    
    lock = threading.Lock()
    t1 = threading.Thread(target=func1)
    t2 = threading.Thread(target=func2)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(a)
    

    例二:

    import time,threading
    account_balance = 500
    r = threading.Lock()#一把锁
    def option_num(num):
        global account_balance
        r.acquire() #上锁
        balance = account_balance
        time.sleep(1)
        balance = balance+num
        account_balance = balance
        r.release() #开锁
    
    t1 = threading.Thread(target=option_num,args=[10000])
    t2 = threading.Thread(target=option_num,args=[-300])
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(account_balance)
    
    • 死锁和递归锁
      什么是死锁呢?在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,这时候就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。
      例子:小明今天去面试,面试官说:“你给我解释一下什么是死锁,我就给你发 offer”。小明一听就不乐意 了,心想你还挺横,于是就说:“你给我发 offer 我就告诉你什么叫死锁。” 诺,这就是死锁,小明和面试官都霸 占有对方的资源不肯松手并且同时在等待对方的资源
    • 死锁 解决办法递归锁
    import threading,time
    lockA = threading.Lock()
    lockB = threading.Lock()
    
    def foo1():
        lockA.acquire()
        print('解释什么是死锁')
        time.sleep(1)
        lockB.acquire()
        print('给你发office')
        time.sleep(1)
        lockA.release()
        lockB.release()
    
    
    def foo2():
        lockB.acquire()
        print('给我发office')
        time.sleep(1)
        lockA.acquire()
        print('我就解释什么是死锁')
        time.sleep(1)
        lockA.release()
        lockB.release()
    
    t1 = threading.Thread(target=foo1)
    t2 = threading.Thread(target=foo2)
    t1.start()
    t2.start()
    
    
    • 递归锁
      为了支持在同一线程中多次请求同一资源,python提供了可重入锁:threading.RLock RLock内部维护这一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以背多次acquire,直到一个线程所有的acquire都被release,其他的线程才能获得资源
    import threading,time
    lockR = threading.RLock()
    # lockB = threading.Lock()
    
    def foo1():
        lockR.acquire()
        print('解释什么是死锁')
        time.sleep(1)
        lockR.acquire()
        print('给你发office')
        time.sleep(1)
        lockR.release()
        lockR.release()
    
    
    def foo2():
        lockR.acquire()
        print('给我发office')
        time.sleep(1)
        lockR.acquire()
        print('我就解释什么是死锁')
        time.sleep(1)
        lockR.release()
        lockR.release()
    
    t1 = threading.Thread(target=foo1)
    t2 = threading.Thread(target=foo2)
    t2.start()
    t1.start()
    
    
    • 线程执行I/O密集型任务的优势(网络请求)
    import threading
    #通过继承threading类来创建线程
    import time
    
    import requests
    
    
    class RequestThread(threading.Thread):
        """发送request请求"""
        #传参数要调用父类的init方法
        def __init__(self,url):
            threading.Thread.__init__(self)
            self.url = url
        def run(self):
            for i in range(100):
                res = requests.get(self.url)
                print(f"当前线程{threading.current_thread()}返回的状态码---{res.status_code}")
    
    s_time = time.time()
    li1 = []
    li = ['q','w','e','r','t']
    for i in li:
        li = RequestThread('https://www.apple.com/')
        li.start()
        li1.append(li)
    for i in li1:
        i.join()
    
    e_time = time.time()
    print(e_time-s_time)
    
    s_time = time.time()
    for i in range(500):
        req = requests.get('https://www.apple.com/')
        print(req.status_code)
    
    e_time = time.time()
    print(e_time-s_time)
    

    相关文章

      网友评论

          本文标题:python多线程

          本文链接:https://www.haomeiwen.com/subject/vhjryhtx.html