zoukankan      html  css  js  c++  java
  • Python基础

    上篇对多线程有一个初步的认识, 常用的要点, 也是对照这 多进程 来试验的. 目的呢, 还是再不断地提醒自己能通俗理解进程和线程的"关系", OS -> 多进程 -> 多线程, (进程 : 线程 , 1 : n) 的关系. 当然从应用的角度, 会考虑到多线程该如何代码实现, 以及几个特性, 如 "主线程会默认等待子线程结束, 才结束", 这跟进程一样的. 于是有了 daemon 的概念...

    下篇呢, 主要从多线程的 共享全局变量 从而引发 资源竞争 问题等, 来对比下多线程和多进程...

    CPython 下, 并没有真正的多线程, 这个是历史性的问题哦, 暂且不谈.

    多线程 - 共享全局变量

    这点跟多进程不同, 多进程不共享全局变量, 通信方式呢, 可以用一个消息队列的方式不断去 "监听" .

    # 多线程共享全局变量
    
    import time
    import threading
    
    lst = []
    
    
    def add_data():
        for i in range(5):
            print("add data:", i)
            lst.append(i)
            time.sleep(2)
    
    
    def get_data():
        while len(lst) <= 4:  # 测试而已
            print("get data: ", lst)
            time.sleep(1)
    
    
    if __name__ == '__main__':
        t1 = threading.Thread(target=add_data)
        t2 = threading.Thread(target=get_data)
    
        t1.start()
        t2.start()
    
        print("main threading done")
    
    
    add data: 0
    get data:  [0]
    main threading done
    get data:  [0]
    add data: 1
    get data:  [0, 1]
    get data:  [0, 1]
    add data: 2
    get data:  [0, 1, 2]
    get data:  [0, 1, 2]
    add data: 3
    get data:  [0, 1, 2, 3]
    get data:  [0, 1, 2, 3]
    add data: 4
    
    

    可将跟进程不同, 多线程是共享全局变量的哦.

    # 多进程 不共享全局变量
    
    import time
    import multiprocessing
    
    lst = []
    
    
    def add_data():
        for i in range(5):
            print("add data:", i)
            lst.append(i)
            time.sleep(2)
    
    
    def get_data():
        while len(lst) <= 4:
            print("get data: ", lst)
            time.sleep(1)
    
    
    if __name__ == '__main__':
        t1 = multiprocessing.Process(target=add_data)
        t2 = multiprocessing.Process(target=get_data)
    
        t1.start()
        t2.start()
    
        print("main processing done")
    
    
    main processing done
    add data: 0
    get data:  []
    get data:  []
    add data: 1
    get data:  []
    get data:  []
    add data: 2
    get data:  []
    get data:  []
    add data: 3
    get data:  []
    

    当然, 关于多线程和多进程在全局变量共不共享的问题上, 其实各有各的特点和通途. 至于多进程不能共享, 因为其实不同的对象嘛或者程序我感觉. 同时能, 不共享能降低程序的耦合性, 也不会增加逻辑理解上的难度哦. 我觉得是设计蛮好的, 原理也暂时不深究. 只是从应用上, 如果非要共享, 建议还是用队列 的方式来作为通信渠道.

    而多线程就不是这样子, 是可以资源共享的, 那必然会带来一个大的问题, 资源的竞争.

    子线程资源竞争

    import time
    import threading
    
    count = 0
    
    
    def task_01():
        for _ in range(1234567):
            global count
            count += 1
        print("count task_02:", count)
    
    
    def task_02():
        for _ in range(1234567):
            global count
            count += 1
        print("count task_01:", count)
    
    
    if __name__ == '__main__':
        t1 = threading.Thread(target=task_01)
        t2 = threading.Thread(target=task_02)
    
        t1.start()
        t2.start()
    
    count task_02: 1405859
    count task_01: 1788620
    

    一看这个数据就不对. 两个线程对 全局变量 count 进行操作时, 状态就乱了呀, 这就是资源竞争的问题. 线程是共享全局变量的, 因此会有这种情况的. 那最简单的解决办法, 就是 线程同步, 保证在同一时刻, 只能有一个线程去对全局加变量进行处理.

    线程等待

    写法上, 即对某个进程, 调用 join( ), 如果这样, 也就变成了单进程了.

    import time
    import threading
    
    count = 0
    
    
    def task_01():
        for _ in range(1234567):
            global count
            count += 1
        print("count task_02:", count)
    
    
    def task_02():
        for _ in range(1234567):
            global count
            count += 1
        print("count task_01:", count)
    
    
    if __name__ == '__main__':
        t1 = threading.Thread(target=task_01)
        t2 = threading.Thread(target=task_02)
    
        t1.start()
        t1.join()  # 等 t1 执行完了再执行 t2, 没有多线程了.
    
        t2.start()
    
    count task_02: 1234567
    count task_01: 2469134
    
    

    这样又变回了单进程, 没有啥意义了.

    互斥锁

    锁的目的, 即为了在多线程中, 防止资源竞争的问题, 但同时也 降低了运行效率 和 容易 死锁.

    • 当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。

    • 每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。

    • 程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

    import threading
    
    # 创建锁, 默认开启
    my_lock = threading.Lock()
    
    count = 0
    
    
    def task_01():
        for _ in range(1234567):
            global count
            # acquire(), 进入锁定的状态
            my_lock.acquire()
            count += 1
            # release(), 解锁
            my_lock.release()
        print("count task_02:", count)
    
    
    def task_02():
        for _ in range(1234567):
            global count
            my_lock.acquire()
            count += 1
            my_lock.release()
        print("count task_01:", count)
    
    
    if __name__ == '__main__':
        t1 = threading.Thread(target=task_01)
        t2 = threading.Thread(target=task_02)
    
        t1.start()
        t2.start()
    

    就到这吧, 多线程的基本点也差不多了, 不想再弄所谓 "死锁" 的情况, 这些都很直观, 就你在某个时刻, 让其进入 锁定的状态, 然后忘了 release, 肯定就被锁死了呀. 就不演示 demo 了, 线程也几乎很少在用, 原因是我用的 CPython的解释器, 有一个历史的bug, 里面好像是说, 就已经有了一个 "锁" 的概念, 因此是没有真正的实现多线程的. 因而我基本不会用, 一般用多进程来整, 反正电脑配置好, 资源随便玩, 不差这点...

    小结

    对于线程和进程, 还可以稍微来比较一下. 一个进程只要有一个线程, 就是这样的关系. 从资源占用的角度来决策, 多进程是 cpu 调度的嘛, 因此多进程资源开销是比较大的, 线程在进程内部, 资源开销是有进程决定的呀. 现在咱的电脑都是多核的, 如果用多进程, 则可充分利用 多核资源. 必须要突出一点很重要的区别:

    • 多进程不共享全局变量 (当然,可借助队列实现通信)
    • 多线程能共享全局变量 (然而, 带来了资源竞争的问题, 可以一用 互斥锁 (Lock) 来解决

    总之不论从写代码角度还是程序的稳定性上, 多进程肯定是占优势的, 我也是优先推荐. 至于资源占用大的问题, 我想, 公司的服务器, 似乎不差我这一点点占用吧.

  • 相关阅读:
    WingIIDE的licese破解方法
    HttpCookie
    Jquery中html()方法 and "click"绑定后代元素
    jquery 选择器多个
    input标签的type为select、radio、checkbox的使用
    c#中?和??使用
    VS中使用附加进程来调试项目
    刷新局部页面
    ant design vue 文件上传的集中页面
    vue 上传超大文件出现Uncaught (in promise) Error: Network Error at createError
  • 原文地址:https://www.cnblogs.com/chenjieyouge/p/12405369.html
Copyright © 2011-2022 走看看