线程

什么是线程

  • 官方定义:

    线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

  • 说人话:

    假如进程是保洁公司,线程就是公司的员工。当公司接到任务时,干活的是员工,而进程负责分配员工。可以好几个员工擦一块玻璃,也可以一个员工收拾一个屋子。

特点

  • 独立调度和分派的基本单位

    一个公司里至少得有一个员工,才能干活。公司分配各项工作给团队,最后团队还是会分配给个人。

  • 轻型实体

    每个线程占用的系统资源非常少。

  • 可并发执行

    多个线程可以同时工作,就像公司里的员工可以一切干活。

  • 共享进程资源

    所有的线程共享该进程所拥有的资源。

    例如: 所有线程地址空间都相同(进程的地址空间),就好比一家公司的所有员工的工作地址都为公司的所在地。

线程与进程的关系

继续用保洁公司举例子

  • 公司本身是进程,公司里又很多员工,每个员工都是一个线程,同时公司里还有很多共享的资源,比如:扫把、墩布、毛巾、饮水机等。
  • 但是与现实模型不同的是,这些人是由多个 cpu 控制的,例如 4 个 cpu 对应 40 个人,cpu 需要切换控制。
  • 真正执行工作的是公司员工,也就是进程的任务靠线程执行
  • 这些人可以并行工作,处理事情。也就是多线程可以同时运行。
  • 当大家访问公共资源的时候会有冲突,例如:都要出去擦玻璃,就剩一条毛巾了,这时就需要排队。在进程中就叫做上锁。

Python3中的多线程

全局解释器锁(GIL)

GIL是啥?

  • GIL并不是Python语言的特性,它是在现实Python解释器时引用的一个概念。

  • GIL只在CPython解释器上存在。作用是保证同一时间内只有一个线程在执行。

  • 解决解释器中多个线程的竞争资源问题

GIL对Python程序有啥影响?

  • Python中同一时刻有且只有一个线程会执行。

    因此,Python中的多线程并不算是真正意义上的多线程。

  • Python中的多个线程由于GIL锁的存在无法利用多核CPU。

    因此,Python中的多线程不适合计算机密集型的程序。

    • 计算密集型程序

      计算密集型又叫CUP密集型,这类程序绝大部分运行时间都消耗在CPU计算上,这个时候,不论你开多少线程,他用的时间都是这么多,甚至比原来时间还长,因为GIL一个时刻只让你执行一个线程,大部分计算密集型任务你分了很多线程但是依然会按照代码顺序线性执行。分很多线程没有什么改善,反而因为代码的冗杂可能更加慢。

    • IO密集型程序

      IO密集型顾名思义就是主要进行I/O操作,90%以上的时间都花费在网络、硬盘、输入输出上了,CPU执行完命令之后其实就没事干了,就可以释放内存来执行下一条命令了,不用让CPU在那干等着,这样就能大大提高程序的运行效率。

改善GIL产生的问题

  • 使用更高版本的解释器,优化对Python的解释
  • 变更解释器,如(JPython),但可能因为相对小众,支持的模块相对较少,开发效率变低
  • 用多进程方案替代多线程

Python3关于多线程的模块

Python的标准库提供了两个模块:_thread和threading

  • _thread

    在Python3之前为thread,有于存在缺陷,不建议使用,在Python3中封装成_thread

  • threading

    thread的继承者,绝大多数情况下使用threading就够了。

多线程使用

  • 直接调用

    把一个函数传入创建Thread实例,然后调用start()开始执行

    import threading
    import time
    def loop():
    #threading.current_thread().name获取当前线程的名字
    print(f"线程( {threading.current_thread().name} )正在执行……")
    num = 0
    while num < 5:
    num += 1
    print(f"线程( {threading.current_thread().name} )>>> {num}")
    time.sleep(1)
    print(f"线程( {threading.current_thread().name}) 执行结束") if __name__ == '__main__':
    print(f"线程( {threading.current_thread().name} )正在执行……")
    # 创建线程实体,target导入的函数,name线程的名字,初始为Tread1,之后类推2,3,4
    # 如果导入函数有参数,使用 arg=() 添加函数参数
    t = threading.Thread(target=loop,name='LoopThread')
    # 启动线程
    t.start()
    # 等待多线程结束
    t.join()
    print(f"线程( {threading.current_thread().name} )执行结束")

    执行结果如下:

    线程( MainThread )正在执行……
    线程( LoopThread )正在执行……
    线程( LoopThread )>>> 1
    线程( LoopThread )>>> 2
    线程( LoopThread )>>> 3
    线程( LoopThread )>>> 4
    线程( LoopThread )>>> 5
    线程( LoopThread) 执行结束
    线程( MainThread )执行结束
  • 继承自threading.Thread调用

    • 直接继承Thread
    • 重写run函数,run函数代表的是真正执行的功能
    • 类实例可以直接运行

    import threading
    import time
    # 1. 类需要继承自threading.Thread
    class MyThread(threading.Thread):
    def __init__(self, arg):
    super(MyThread, self).__init__()
    self.arg = arg # 2 必须重写run函数,run函数代表的是真正执行的功能
    def run(self):
    time.sleep(2)
    print(f"Run >>> {self.arg}") for i in range(1,4):
    t = MyThread(i)
    t.start()
    t.join() print("End")

    执行结果如下:

    Run  >>>  1
    Run >>> 2
    Run >>> 3
    End
  • 守护线程

    • 不设置守护线程

      import time
      import threading def fun():
      print("启动Fun")
      time.sleep(2)
      print("结束Fun") print("启动Main")
      t = threading.Thread(target=fun)
      t.start()
      time.sleep(1)
      print("结束Main")

      运行结果

      启动Main
      启动Fun
      结束Main
      结束Fun
    • 设置守护线程

      import time
      import threading def fun():
      print("启动Fun")
      time.sleep(2)
      print("结束Fun") print("启动Main")
      t = threading.Thread(target=fun)
      t.setDaemon(True)
      t.start()
      time.sleep(1)
      print("结束Main")

      运行结果

      启动Main
      启动Fun
      结束Main

      如果你设置一个线程为守护线程,,就表示你认为此线程不重要,在进程退出的时候,不用等待这个线程即可退出。

      thread.setDaemon(True|False)表示此线程是否为守护线程。但此语句必须加在thread.start()之前

  • 常用函数

    • threading.enumerate():

      返回一个包含正在运行的线程的list

    • threading.activeCount():

      返回正在运行的线程数量,效果跟 len(threading.enumerate)相同

    • thr.setName(): 给线程设置名字

      thr.getName(): 得到线程的名字

      • thr表示线程实例,如t1.getName()

      • thr.getName()获取指定线程的名字,threading.current_thread().name获取当前线程的名字

共享变量

多线程同时访问同一变量时,会产生共享变量的问题,造成变量冲突产生问题。

  • 实例

    执行多线程,对同一变量进行加减操作,代码如下。

    import threading
    
    sum = 0
    loopSum = 1000000 def myAdd():
    global sum, loopSum
    for i in range(1, loopSum):
    sum += 1 def myMinu():
    global sum, loopSum
    for i in range(1, loopSum):
    sum -= 1 if __name__ == '__main__':
    print(f"Starting ....{sum}") t1 = threading.Thread(target=myAdd, args=())
    t2 = threading.Thread(target=myMinu, args=()) t1.start()
    t2.start() t1.join()
    t2.join() print(f"Done .... {sum}")

    两次分别如下执行结果如下:

    Starting ....0
    Done .... -278763
    Starting ....0
    Done .... 662850

    可以发现,运算结果与我们传统认知不同。原因是Python在内部实现运算时是一个复杂的过程,同时对sum进行修改时产生的相互干扰,故出现未知错误,导致结果出错。

  • 解决方法:上锁

    锁(Lock):是一个标志,表示一个线程在占用一些共享资源.

    • 那些资源需要上锁?

      需要被共享使用的,可能产生使用冲突的

    • 注意:

      避免产生死锁,导致程序陷入死循环

    • 线程安全问题:

      • 如果一个资源,他对于多线程来讲,不用加锁也不会引起任何问题,则称为线程安全。
      • 线程不安全变量类型: list, set, dict
      • 线程安全变量类型: queue

    使用案例,代码如下:

    import threading
    
    sum = 0
    loopSum = 1000000
    lock = threading.Lock()
    def myAdd():
    global sum, loopSum
    for i in range(1, loopSum):
    # 上锁,申请锁
    lock.acquire()
    sum += 1
    # 释放锁
    lock.release() def myMinu():
    global sum, loopSum for i in range(1, loopSum):
    lock.acquire()
    sum -= 1
    lock.release() if __name__ == '__main__':
    print(f"Starting ....{sum}") t1 = threading.Thread(target=myAdd, args=())
    t2 = threading.Thread(target=myMinu, args=()) t1.start()
    t2.start() t1.join()
    t2.join() print(f"Done .... {sum}")

    执行结果:

    Starting ....0
    Done .... 0

Python3 多线程编程 - 学习笔记的更多相关文章

  1. 多线程编程学习笔记——async和await(一)

    接上文 多线程编程学习笔记——任务并行库(一) 接上文 多线程编程学习笔记——任务并行库(二) 接上文 多线程编程学习笔记——任务并行库(三) 接上文 多线程编程学习笔记——任务并行库(四) 通过前面 ...

  2. 多线程编程学习笔记——async和await(二)

    接上文 多线程编程学习笔记——async和await(一) 三.   对连续的异步任务使用await操作符 本示例学习如何阅读有多个await方法方法时,程序的实际流程是怎么样的,理解await的异步 ...

  3. 多线程编程学习笔记——async和await(三)

    接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五.   处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...

  4. 多线程编程学习笔记——使用异步IO(一)

    接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...

  5. 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端

    接上文 多线程编程学习笔记——使用异步IO 二.   编写一个异步的HTTP服务器和客户端 本节展示了如何编写一个简单的异步HTTP服务器. 1.程序代码如下. using System; using ...

  6. 多线程编程学习笔记——异步调用WCF服务

    接上文 多线程编程学习笔记——使用异步IO 接上文 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端 接上文 多线程编程学习笔记——异步操作数据库 本示例描述了如何创建一个WCF服务,并宿主 ...

  7. 多线程编程学习笔记——使用异步IO

    接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...

  8. Java多线程编程(学习笔记)

    一.说明 周末抽空重新学习了下多线程,为了方便以后查阅,写下学习笔记. 有效利用多线程的关键是理解程序是并发执行而不是串行执行的.例如:程序中有两个子系统需要并发执行,这时候需要利用多线程编程. 通过 ...

  9. [Java123] JDBC and Multi-Threading 多线程编程学习笔记

    项目实际需求:DB交互使用多线程实现 多线程编程基础:1.5  :( (假设总分10) 计划一个半月从头学习梳理Java多线程编程基础以及Oracle数据库交互相关的多线程实现 学习如何通过代码去验证 ...

随机推荐

  1. Batch - FOR %%a %%b

    总结 %%a refers to the name of the variable your for loop will write to. Quoted from for /?: FOR %vari ...

  2. java 接受带有中文的get请求文件下载时的问题

    参数是接受到了 , debug的时候也能看的到 , 但是奇怪的是就是找不到文件 @ApiOperation(value = "文件下载/图片预览") @GetMapping(val ...

  3. flume的安装和使用

    1.下载 [linyouyi@hadoop01 software]$ wget https://mirrors.aliyun.com/apache/flume/1.9.0/apache-flume-1 ...

  4. 关于SecureCRT链接服务器出现乱码的问题

    连接到服务器,选择上方的“选项”->“会话选项”->“外观”->右边的字符编码->utf-8

  5. NX二次开发-将工程图上的每个视图导出PNG图片

    大概思路是将每个视图导出PDF,在调另一个项目的EXE(PDF转PNG) //ExportDrawViewPng // Mandatory UF Includes #include <uf.h& ...

  6. Apache负载均衡

    Apache负载均衡 Apache也是可以实现负载均衡的.Apache的负载均衡主要是通过mod_proxy_balancer实现的.那么,apache负载均衡的配置方法是什么样的? 在apache的 ...

  7. Api:目录

    ylbtech-Api:目录 1.返回顶部   2.返回顶部   3.返回顶部   4.返回顶部   5.返回顶部     6.返回顶部   作者:ylbtech出处:http://ylbtech.c ...

  8. SPSS进行数据分析的一般步骤

    SPSS进行数据分析的一般步骤 利用SPSS进行数据分析的一般步骤 利用SPSS进行数据分析的关键在于遵循数据分析的一般步骤,但涉及的方面会相对较少.主要集中在以下几个阶段. 1.SPSS数据的准备阶 ...

  9. Memory barrier,

    A memory barrier, also known as a membar, memory fence or fence instruction, 是一种屏障指令,它使中央处理单元(CPU)或编 ...

  10. UVA 240 Variable Radix Huffman Encoding

    题目链接:https://vjudge.net/problem/UVA-240 题目大意 哈夫曼编码是一种最优编码方法.根据已知源字母表中字符出现的频率,将源字母表中字符编码为目标字母表中字符,最优的 ...