• 并发编程小结


    一、到底什么是线程?什么是进程?

    Python自己没有这玩意,Python中调用的操作系统的线程和进程。

    二、Python多线程情况下:

    计算密集型操作:效率低,Python内置的一个全局解释器锁,锁的作用就是保证同一时刻一个进程中只有一个线程可以被cpu调度,多线程无法利用多核优势,可以通过多进程方式解决,但是比较浪费资源。
    IO操作:效率高

    三、Python多进程的情况下:

    计算密集型操作:效率高(浪费资源),不得已而为之。
    IO操作:效率高(浪费资源)

    四、为什么有这把GIL锁?

    Python语言的创始人在开发这门语言时,目的快速把语言开发出来,如果加上GIL锁(C语言加锁),切换时按照100条字节指令来进行线程间的切换。

    五、Python中线程和进程(GIL锁)

    GIL锁,全局解释器锁。用于限制一个进程中同一时刻只有一个线程被cpu调度。
    扩展:默认GIL锁在执行100个cpu指令(过期时间)。
    查看GIL切换的指令个数

    import sys
    v1 = sys。getcheckinterval()
    print(v1)
    

    六、为什么要创建线程?

    由于线程是cpu工作的最小单元,创建线程可以利用多核优势实现并行操作(Java/C#)。
    注意:线程是为了工作。

    七、为什么要创建进程?

    进程和进程之间做数据隔离(Java/C#)。

    注意:进程是为了提供环境让线程工作。

    八、进程和线程的区别?

    进程是资源分配的最小单位,线程是程序执行的最小单位。

    进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段,堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程小很多,同时创建一个线程的开销比进程要小很多。

    线程之间的通信更方便,同一进程下的线程共享全局变量,静态变量,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

    但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

    也正是由于GIL锁的原因:IO密集型操作可以使用多线程,计算密集型可以使用多进程。

    九、线程创建的越多越好吗?

    不好。线程之间进行切换时,要做上下文管理。

    十、生产者消费者模型解决了什么问题?

    不用一直等待的问题。

    十一、Lock和RLock的区别?

    RLock可以多次加锁。

    十二、进程和线程以及协程的区别?

    进程是cpu资源分配的最小单元,一个进程中可以有多个线程。

    线程是cpu计算的最小单元。

    对于Python来说他的进程和线程和其他语言有差异,是有GIL锁。

    GIL锁保证一个进程中同一时刻只有一个线程被cpu调度。

    注意:IO密集型操作可以使用多线程,计算密集型可以使用多进程。

    协程,是由程序员创造出来的一个不是真实存在的东西。

    协程,是微线程,对一个线程进程分片,使得线程在代码块之间进行来回切换执行,而不是在原来逐行执行。

    十三、IO多路复用作用?

    检测多个socket是否已经发生变化(是否已经连接成功/是否已经获取数据)(可读/可写)IO多路复用作用?

    检测多个socket是否发生变化。

    操作系统检测socket是否发生变化,有三种模式:

    • select:最多1024个socket;循环去检测。
    • poll:不限制监听socket个数;循环去检测(水平触发)。
    • epoll:不限制监听socket个数;回调方式(边缘触发)。

    Python模块:

    • select.select
    • select.epoll

    十四、socket默认是否是阻塞的?阻塞体现在哪里?

    默认是阻塞,填在等待消息和连接

    十五、什么是异步非阻塞?

    非阻塞,不等待。比如创建socket对某个地址进行connect、获取接收数据recv时默认都会等待(连接成功或接收到数据),才执行后续操作。
    如果设置setblocking(False),以上两个过程就不再等待,但是会报BlockingIOError的错误,只要捕获即可。

    异步,通知,执行完成之后自动执行回调函数或自动执行某些操作(通知)。比如做爬虫中向某个地址baidu。com发送请求,当请求执行完成之后自执行回调函数。

    十六、什么是同步阻塞?

    • 阻塞:等
    • 同步:按照顺序逐步执行

    十七、什么是协程?

    协程也可以称为“微线程”,就是开发者控制线程执行流程,控制先执行某段代码然后再切换到另外函执行代码...来回切换。

    十八、协程可以提高并发吗?

    协程自己本身无法实现并发(甚至性能会降低)。
    协程+IO切换性能提高。

    十九、提高并发方案:

    • 多进程
    • 多线程
    • 单线程提供并发

    二十、单线程提供并发

    20.1 基于IO多路复用+socket非阻塞

    实现并发请求(一个线程100个请求)

    import socket
    # 创建socket
    client = socket.socket()
    # 将原来阻塞的位置变成非阻塞(报错)
    client.setblocking(False)
    
    # 百度创建连接: 阻塞
    try:
        # 执行了但报错了
        client.connect(('www.baidu.com',80))
    except BlockingIOError as e:
        pass
    
    # 检测到已经连接成功
    
    # 问百度我要什么?
    client.sendall(b'GET /s?wd=alex HTTP/1.0
    host:www.baidu.com
    
    ')
    
    # 我等着接收百度给我的回复
    chunk_list = []
    while True:
        # 将原来阻塞的位置变成非阻塞(报错)
        chunk = client.recv(8096) 
        if not chunk:
            break
        chunk_list.append(chunk)
    
    body = b''.join(chunk_list)
    print(body.decode('utf-8'))
    

    20.2 协程+IO切换:gevent

    from gevent import monkey
    # 以后代码中遇到IO都会自动执行greenlet的switch进行切换
    monkey.patch_all() 
    import requests
    import gevent
    
    
    def get_page1(url):
        ret = requests.get(url)
        print(url,ret.content)
    
    def get_page2(url):
        ret = requests.get(url)
        print(url,ret.content)
    
    def get_page3(url):
        ret = requests.get(url)
        print(url,ret.content)
    
    gevent.joinall([
        gevent.spawn(get_page1, 'https://www.python.org/'), # 协程1
        gevent.spawn(get_page2, 'https://www.yahoo.com/'),  # 协程2
        gevent.spawn(get_page3, 'https://github.com/'),     # 协程3
    ])
    

    20.3 基于事件循环的异步

    执行完某个人物后自动调用我给他的函数,非阻塞

    20.3.1 方法一

    import socket
    import select
    
    # 百度创建连接:非阻塞
    client1 = socket.socket()
    client1.setblocking(False)
    try:
        client1.connect(('www.baidu.com', 80))
    except BlockingIOError as e:
        pass
    
    # 搜狗创建连接:非阻塞
    client2 = socket.socket()
    client2.setblocking(False)
    try:
        client2.connect(('www.sogou.com', 80))
    except BlockingIOError as e:
        pass
    
    # GitHub创建连接:非阻塞
    client3 = socket.socket()
    client3.setblocking(False)
    try:
        client3.connect(('www.github.com', 80))
    except BlockingIOError as e:
        pass
    
    # 创建socket列表:socket_list
    socket_list = [client1, client2, client3]
    # 创建connect列表:conn_list
    conn_list = [client1, client2, client3]
    
    while True:
        rlist, wlist, elist = select.select(socket_list, conn_list, [], 0.005)
        # rlist中表示已近获取数据的socket对象
        # wlist中表示已经连接成功的socket对象
        # elist中表示出现错误的socket对象
        for sk in wlist:
            if sk == client1:
                sk.sendall(b'GET /s?wd=alex HTTP/1.0
    host:www.baidu.com
    
    ')
            elif sk == client2:
                sk.sendall(b'GET /web?query=fdf HTTP/1.0
    host:www.sogou.com
    
    ')
            else:
                sk.sendall(b'GET /s?wd=alex HTTP/1.0
    host:www.oldboyedu.com
    
    ')
            conn_list.remove(sk)
        for sk in rlist:
            chunk_list = []
            while True:
                try:
                    chunk = sk.recv(8096)
                    if not chunk:
                        break
                    chunk_list.append(chunk)
                except BlockingIOError as e:
                    break
            body = b''.join(chunk_list)
            # print(body.decode('utf-8'))
            print('------------>', body)
            sk.close()
            socket_list.remove(sk)
        if not socket_list:
            break
    

    20.3.2 方法二

    import socket
    import select
    
    class Req(object):
        def __init__(self,sk,func):
            self.sock = sk
            self.func = func
    
        def fileno(self):
            return self.sock.fileno()
    
    
    class Nb(object):
    
        def __init__(self):
            self.conn_list = []
            self.socket_list = []
    
        def add(self,url,func):
            # 创建socket客户端
            client = socket.socket()
            # 非阻塞
            client.setblocking(False)
            try:
                # 创建连接
                client.connect((url, 80))
                # 异常处理
            except BlockingIOError as e:
                pass
            obj = Req(client,func)
            # 连接列表
            self.conn_list.append(obj)
            # socket列表
            self.socket_list.append(obj)
    
        def run(self):
    
            while True:
                rlist,wlist,elist = select.select(self.socket_list,self.conn_list,[],0.005)
                # wlist中表示已经连接成功的req对象
                for sk in wlist:
                    # 发生变换的req对象
                    sk.sock.sendall(b'GET /s?wd=alex HTTP/1.0
    host:www.baidu.com
    
    ')
                    self.conn_list.remove(sk)
                for sk in rlist:
                    chunk_list = []
                    while True:
                        try:
                            chunk = sk.sock.recv(8096)
                            if not chunk:
                                break
                            chunk_list.append(chunk)
                        except BlockingIOError as e:
                            break
                    body = b''.join(chunk_list)
                    # print(body.decode('utf-8'))
                    sk.func(body)
                    sk.sock.close()
                    self.socket_list.remove(sk)
                if not self.socket_list:
                    break
    
    
    def baidu_repsonse(body):
        print('百度下载结果:',body)
    
    def sogou_repsonse(body):
        print('搜狗下载结果:', body)
    
    def github_repsonse(body):
        print('GITHUB下载结果:', body)
    
    
    t1 = Nb()
    t1.add('www.baidu.com',baidu_repsonse)
    t1.add('www.sogou.com',sogou_repsonse)
    t1.add('www.github.com',oldboyedu_repsonse)
    t1.run()
    

    20.4 基于事件循环的异步非阻塞框架

    如Twisted框架,scrapy框架(单线程完成并发)

  • 相关阅读:
    使用sql语句查询表结构
    plsql出现录相机后卡屏解决方法
    oracle的“ORA-01480:STR绑定值的结尾Null字符缺失”错误
    oracle创建表空间并对用户赋权
    Scrapy安装错误(error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools)
    震惊你不知道的python
    django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module: No module named 'MySQLdb'
    python3 ImportError: No module named 'ConfigParser'
    python import报错
    No migrations to apply(django不能创建数据库中的表的问题)
  • 原文地址:https://www.cnblogs.com/zhouxuchong/p/11576292.html
Copyright © 2020-2023  润新知