• 17-多线程


    GIL

    • global interpreter lock 全局解释器锁
    • 作用
      • 保证只有一个线程执行
      • 因为有GIL的存在,多个线程也只会占用一个CPU
      • 进程会使用多个CPU

    创建线程

    • 线程:主线程/分线程

    • 方法1

      • # 线程是CPU分配资源的基本单位。一个程序开始运行,就变成了一个进程,而一个进程相当于一个或者多个线程。当没有多线程编程时,一个进程也是一个主线程,当有多线程编程时,一个进程包含多个线程(包括主线程)。使用线程可以实现程序的并发。
        
        import _thread
        import time
        import threading
        
        # 第一种方式
        # 子线程/分线程
        def thread1(*args):
            print('线程1:', args)
            print('子线程', threading.current_thread().name)  # 子线程 Dummy-1
        
        def creat_thread1():
            # 当前线程名  # MainThread
            print('主线程', threading.current_thread().name)
            # 创建子线程
            # 守护线程:子线程会随着主线程的结束而结束
            # 主公就是主线程,忠臣就是子线程
            _thread.start_new_thread(thread1, ('韩信', '刘邦'))
        
            time.sleep(20)
        
        
        # 第二种方式
        def thread2(*args):
            print('子线程:', args)
            print('子线程:', threading.current_thread().name)  # 子线程: 线程1
        
        def create_thread2():
            t = threading.Thread(target=thread2, name='线程1', args=('李白', '杜甫'))
            t.start()  # 启动线程
            print('hello')
        
        
        # 第三种方式
        # 自定义线程
        # 继承
        class MyThread(threading.Thread):
            def __init__(self, url):
                super().__init__()
                self.url = url
        
                # 重写run方法:会自动调用,这个函数就是子线程的函数,类似target
            def run(self):
                print('子线程:', threading.current_thread().name)  # 子线程: Thread-1
        
        def create_thread3():
            t = MyThread('http://www.baidu.com')
            t.start()
        
        if __name__ == '__main__':
            # creat_thread1()
            # create_thread2()
            create_thread3()
        

    多线程

    • 同步:在一个线程上执行

    • 异步:在不同的线程上执行,在异步的基础上加上t.join()(阻塞),即变成同步

      • # 等待所有线程全部执行完
        for t in t_list:
        t.join()  # 阻塞,等到最后一个线程结束
        
    • ​ Python的多线程是假的多线程,但是仍然会提高效率

    • 线程的其他属性和方法

      • print(t.name, t.getName()) # 当前线程的名字
      • print(t.isDaemon()) # 是否为守护线程
      • print(t.ident) # 线程号
      • print(t.is_alive()) # 线程是否在运行
      • print(threading.active_count()) # 正在运行的线程数量
      • print(threading.enumerate()) # 列举所有正在运行的线程

    cpu密集型(计算密集型)&I/O密集型

    • 计算密集型任务由于主要消耗CPU资源,代码运行效率至关重要,C语言编写;

      • 计算密集型:建议使用多进程(多进程可以使用到多核)
    • IO密集型,涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部时间都在等待IO操作完成,99%的时间花费在IO上,脚本语言是首选,C语言最差。

      • IO密集型:Input Output(一般是比较耗时的操作),建议使用多线程。
    • 多线程爬虫

      • # 使用多线程爬取深圳所有区的房源,并分别保存到单独的以区为文件名的html中,如: 南山区.html
        # (爬取每个区的第一页即可)
        # https://sz.lianjia.com/ershoufang/pg1/
        import re
        import threading
        import requests
        
        # 模拟浏览器
        headers = {
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36"
        }
        
        # 获取所有区
        def get_area():
            url = 'https://sz.lianjia.com/ershoufang/pg1/'
            # 获取网页数据
            response = requests.get(url, headers=headers)
            content = response.text
            # print(content)
            # 获取所有区
            pattern = r'data-role="ershoufang"(.*?)</div>'
            area_str = re.findall(pattern, content, re.S)[0]
            print(area_str)
        
            pattern2 = r'href="(.*?)"'
            area_url_list = re.findall(pattern2, area_str, re.S)
            # print(area_url_list)
            pattern3 = r'">(.*?)</a>'
            area_name_list = re.findall(pattern3, area_str, re.S)
            # print(area_name_list)
            return area_url_list, area_name_list
        
        # 子线程
        def get_data(url, name):
            # 每个区的url网址
            url = 'https://sz.lianjia.com' + url
            response = requests.get(url, headers=headers)
        
            with open(f'lianjia/{name}.html', 'wb') as fp:
                fp.write(response.content)  # 写入二进制
        
        if __name__ == '__main__':
            # 获取所有区
            area_url_list, area_name_list = get_area()
            for i in range(len(area_url_list)):
                name = area_name_list[i]
                url = area_url_list[i]
        
                t = threading.Thread(target=get_data, args=(url, name))  # 注意传入变量的顺序
                t.start()
        

    线程冲突即线程锁

    • 线程冲突

      • n = 0
        def f():
            global n
            for _ in range(1000000):
                n += 1
            print(n)
            
        def create_thread():
            # 同时开启5个线程,同时访问同一个变量n
            for _ in range(5):
                t = threading.Thread(target=f)
                t.start()
        
        create_thread()
        
      • 多个线程同时操作一个资源时,可能造成资源混乱

    • 线程锁/互斥锁

      • 解决线程冲突

        • 自动加锁,用完后自动解锁,加锁过程种其他线程无法访问

        • 对资源加锁

          • import threading
            # 线程冲突:多个线程同时操作一个资源时,可能造成资源混乱
            # 线程锁/互斥锁:解决线程冲突
            
            # 死锁现象:多个线程同时操作多个资源时
            # 比如:
            # 线程1   线程2
            #   A       B
            # 递归锁/重用锁:解决死锁
            
            n = 0
            def f():
                global n
                for _ in range(1000000):
                    n += 1
                print(n)
            
            # 线程冲突
            def create_thread():
                # 同时开启5个线程,同时访问同一个变量n
                for _ in range(5):
                    t = threading.Thread(target=f)
                    t.start()
                    # t.join()  # 同步
            
            # 线程锁:解决线程冲突
            lock = threading.Lock()
            # 递归锁:解决死锁
            # rlock = threading.RLock()
            
            
            def f2():
                # 自动加锁,用完后会自动解锁
                # 加锁过程种其他线程无法访问
                # with lock:
                # with rlock:  # 递归锁的使用
                #     global n
                #     for _ in range(1000000):
                #         n += 1
                #     print(n)
            
                lock.acquire()  # 加锁
                global n
                for _ in range(1000000):
                    n += 1
                print(n)
            
                lock.release()  # 解锁
            
            # 线程冲突
            def create_thread2():
                # 同时开启5个线程,同时访问同一个变量n
                for _ in range(5):
                    t = threading.Thread(target=f2)
                    t.start()
            
            if __name__ == '__main__':
                # 线程冲突
                # create_thread()
                # 线程锁
                create_thread2()
            

    信号量

    • # 信号量
      import random
      import threading
      
      # 信号量:控制线程的最大并发数,每次最多5个线程同时执行
      import time
      
      sem = threading.Semaphore(5)
      
      def fn(*args):
          with sem:
              print('子线程:', args)
              time.sleep(3+random.random())
      
      if __name__ == '__main__':
          for i in range(1, 21):
              threading.Thread(target=fn, args=(f'Thread-{i}',)).start()
      
  • 相关阅读:
    Session共享的解决方案
    用IIS配置反向代理
    authorization配置
    git之https或http方式设置记住用户名和密码的方法
    微信分享接口
    为你的Visual Studio单独设置代理服务器
    HTTP错误404.13
    MVC5的AuthorizeAttribute详解
    【MVC5】画面多按钮提交
    PetaPoco dynamic
  • 原文地址:https://www.cnblogs.com/lotuslaw/p/14022916.html
Copyright © 2020-2023  润新知