Python高级 — 并发编程

2021/10/17 11:39:40

本文主要是介绍Python高级 — 并发编程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1.对多进程,多线程,协程的理解

  • 进程:一个运行的程序(代码)就是一个进程,没有运行的代码叫程序,进程是系统资源分配的最小单位,进程拥有自己独立的内存空间,所有进程间数据不共享,开销大。
  • 线程: cpu调度执行的最小单位,也叫执行路径,不能独立存在,依赖进程存在,一个进程至少有一个线程,叫主线程,而多个线程共享内存(数据共享,共享全局变量),从而极大地提高了程序的运行效率。
  • 协程: 是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操中栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
        进程和线程的切换时由系统决定,而协程由我们程序员自己决定,而模块gevent下切换是遇到了耗时操作时才会切换 。         三者的关系:进程里有线程,线程里有协程。

2.Python异步使用场景

        1、 不涉及共享资源,获对共享资源只读,即非互斥操作         2、 没有时序上的严格关系

        3、 不需要原子操作,或可以通过其他方式控制原子性

 

        4、 常用于 IO 操作等耗时操作,因为比较影响客户体验和使用性能         5、 不影响主线程逻辑

3.多线程共同操作同一个数据,使用互斥锁同步

import threading 
import time

class MyThread(threading.Thread): 
    def run(self):
        global num 
        time.sleep(1)
        
        if mutex.acquire(1): 
            num +=1
            msg = self.name + 'set num to ' +str(num) 
            print(msg)
            mutex.release()
num = 0
mutex = threading.Lock() 
def test():
    for i in range(5): 
        t = MyThread() 
        t.start()
        
if __name__ =="__main__": 
    test()
        Thread-9set num to 1
        Thread-7set num to 2
        Thread-10set num to 3
        Thread-6set num to 4
        Thread-8set num to 5

4.多线程竞争

        线程是非独立的,同一个进程里线程是数据共享的,当各个线程访问数据资源时会出现竞争状态即:数据几乎同步会被多个线程占用,造成数据混乱,即所谓的线程不安全         那么怎么解决多线程竞争问题?--- 锁         锁的好处: 确保了某段关键代码(共享数据资源)只能由一个线程从头到尾完整地执行能解决多线程资源竞争下的原子操作问题。         锁的坏处: 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了 。         锁的致命问题: 死锁。

5.Python的线程同步

        一、 setDaemon(False)

        当一个进程启动之后,会默认产生一个主线程,因为线程是程序执行的最小单位,当设置多线程时,主线程会创建多个子线程,在Python 中,默认情况下就是 setDaemon(False), 主线程执行完自己的任务以 后,就退出了,此时子线程会继续执行自己的任务,直到自己的任务结束。
import threading 
import time

def thread():
    time.sleep(2)
    print('---子线程结束---')
    
def main():
    t1 = threading.Thread(target=thread) 
    t1.start()
    print('---主线程--结束')
    
if __name__ == '__main__': 
    main()
    
# 执行结果
---主线程--结束
---子线程结束---
        二、 setDaemonTrue)                  当我们使用setDaemon(True)时,这是子线程为守护线程,主线程一旦执行结束,则全部子线程被强制 终止。
import threading 
import time

def thread():
    time.sleep(10)
    print('---子线程结束---')
    
def main():
    t1 = threading.Thread(target=thread) 
    t1.setDaemon(True)  # 设置子线程守护主线程
    t1.start()
    print('---主线程--结束')
    
if __name__ == '__main__': 
    main()
    
# 执行结果
# ---主线程--结束  # 只有主线程结束,子线程来不及执行就被强制结束
        三、 join (线程同步 )         join 所完成的工作就是线程同步,即主线程任务结束以后,进入堵塞状态,一直等待所有的子线程结束以后,主线程再终止。         当设置守护线程时,含义是主线程对于子线程等待timeout 的时间将会杀死该子线程,最后退出程序, 所以说,如果有 10 个子线程,全部的等待时间就是每个 timeout 的累加和,简单的来说,就是给每个子 线程一个 timeou t的时间,让他去执行,时间一到,不管任务有没有完成,直接杀死。         没有设置守护线程时,主线程将会等待timeout 的累加和这样的一段时间,时间一到,主线程结束,但 是并没有杀死子线程,子线程依然可以继续执行,直到子线程全部结束,程序退出。
import threading 
import time

def thread():
    time.sleep(2)
    print('---子线程结束---')
    
def main():
    t1 = threading.Thread(target=thread)
    t1.setDaemon(True) 
    t1.start()
    t1.join(timeout=1)
    # 1 线程同步,主线程堵塞1s 然后主线程结束,子线程继续执行
    # 2 如果不设置timeout参数就等子线程结束主线程再结束
    # 3 如果设置了setDaemon=True和timeout=1主线程等待1s后会强制杀死子线程,然后主线程结束
    print('---主线程结束---')
    
if __name__ == '__main__': 
    main()

# 执行结果
# ---主线程结束---

6.什么是锁,有哪几种锁?

        锁(Lock)python提供的对线程控制的对象。有互斥锁,可重入锁,死锁。

7.死锁

        若干子线程在系统资源竞争时,都在等待对方对某部分资源解除占用状态,结果是谁也不愿先解锁,互相干等着,程序无法执行下去,这就是死锁。

8.假多线程

        GIL锁 全局解释器锁         作用: 限制多线程同时执行,保证同一时间只有一个线程执行,所以cython 里的多线程其实是伪多线程!              

9.多线程交互访问数据,怎么避免重读?

        创建一个已访问数据列表,用于存储已经访问过的数据,并加上互斥锁,在多线程访问数据的时候先查看数据是否在已访问的列表中,若已存在就直接跳过。

10.什么是线程安全,什么是互斥锁?

        每个对象都对应于一个可称为 互斥锁 的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。         同一进程中的多线程之间是共享系统资源的,多个线程同时对一个对象进行操作,一个线程操作尚未结束,另一线程已经对其进行操作,导致最终结果出现错误,此时需要对被操作对象添加互斥锁,保证每 个线程对该对象的操作都得到正确的结果。

11.同步,异步,阻塞,非阻塞

  • 同步: 多个任务之间有先后顺序执行,一个执行完下个才能执行。
  • 异步: 多个任务之间没有先后顺序,可以同时执行,有时候一个任务可能要在必要的时候获取另一个同时执行的任务的结果,这个就叫回调!
  • 阻塞: 如果卡住了调用者,调用者不能继续往下执行,就是说调用者阻塞了。
  • 非阻塞: 如果不会卡住,可以继续执行,就是说非阻塞的。
        同步异步相对于多任务而言,阻塞非阻塞相对于代码执行而言。

12.什么是僵尸进程和孤儿进程?怎么避免僵尸进程?

        孤儿进程: 父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被init 进程(进程号为 1 )所收养,并由 init 进程对他们完成状态收集工作。         僵尸进程: 进程使用fork 创建子进程,如果子进程退出,而父进程并没有调用 wait waitpid 获取子进 程的状态信息,那么子进程的进程描述符仍然保存在系统中的这些进程是僵尸进程。         避免僵尸进程的方法:         1.fork 两次用孙子进程去完成子进程的任务         2.wait() 函数使父进程阻塞         3.使用信号量,在 signal handler 中调用 waitpid, 这样父进程不用阻塞

13.python中进程与线程的使用场景

        多进程适合在CPU 密集操作( cpu 操作指令比较多,如位多的的浮点运算)。         多线程适合在IO 密性型操作(读写数据操作比多的的,比如爬虫)。

14.线程是并发还是并行,进程是并发还是并行?

        线程是并发,进程是并行;         进程之间互相独立,是系统分配资源的最小单位,同一个线程中的所有线程共享资源。

15.并行(parallel)和并发(concurrency)

        并行: 同一时刻多个任务同时在运行。 不会在同一时刻同时运行,存在交替执行的情况。         实现并行的库有: multiprocessing         实现并发的库有: threading         程序需要执行较多的读写、请求和回复任务的需要大量的IO 操作, IO 密集型操作使用并发更好。         CPU运算量大的程序,使用并行会更好。

16.IO密集型和CPU密集型区别

        IO密集型: 系统运行,大部分的状况是 CPU 在等 I/O (硬盘 / 内存)的读 / 写。         CPU密集型: 大部分时间用来做计算,逻辑判断等 CPU 动作的程序称之 CPU 密集型。

17.python asyncio的原理

        asyncio这个库就是使用 pythonyield 这个可以打断保存当前函数的上下文的机制, 封装好了 selector摆脱掉了复杂的回调关系。

这篇关于Python高级 — 并发编程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程