python线程入门

正常情况下,我们在启动一个程序的时候。这个程序会先启动一个进程,启动之后这个进程会启动起来一个线程。这个线程再去处理事务。也就是说真正干活的是线程,进程这玩意只负责向系统要内存,要资源但是进程自己是不干活的。默认情况下只有一个进程只会拉起来一个线程。

多线程顾名思义,就是同样在一个进程的情况同时拉起来多个线程。真正干活的是线程。进程与线程的关系就像是工厂和工人的关系, 要想一个工厂运行起来,至少有一个工,当然如果工人多, 那么效率就变高了。因为只有一个进程,所以多线程在提高效率的同时,并没有向系统伸手要更多的内存资源。因此使用起来性价比还是很高的。但是虽然多线程不会消耗更多的内存,但是每个线程却需要CPU的的参与。

可以这样理解: 工厂虽然是固定的大小,可以容纳很多工人取干活, 但是工人干活儿需要人来协调, 如果工人太多, 对于一个固定的厂, CPU相当于厂长, 厂长的精力也是有限的, 当厂长(CPU忙不过不过来的时候效率也一样会有影响. 所以工人(线程)的数量最好还是在厂长(cpu)的能力(内核数)范围之内比较好。

线程与进程

  • 硬件发展:

    • cpu 切片,由之前的串行处理,到后来实行分片,同时执行一颗线程,处理效率更快.多核CPU 同一时刻可以执行多个线程。
  • 软件发展:

    1. 单进程单线程

    2. 多线程单进程的程序,

      • 同一个进程下的多个线程可能在多颗CPU上执行,一个线程不可能同时在多个cpu上。(其他语言会出现)
      • Python语言不会出现同一个进程下的多线程同时出现在多个CPU上,全局解释器锁GRL. 限定了当一个进程下的多个线程处理的时候,只能在一个CPU上执行,当一个线程被处理的时候,其他的线程只会等候;这样的缺点,处理效率很低。
    3. 多线程多进程的程序。

      进程的开销通常比线程昂贵,因为线程自动共享内存地址空间和文件描述符. 意味着, 创建进程比创建线程会花费更多的资源和时间

    4. 在执行一些sleep/read/write/recv/send这些会导致阻塞的函数时,当前线程会主动放弃GIL,然后调用相应的系统API,完成后再重新申请GIL。因此,GIL也并不是导致Python的多线程完全没用,在一些IO等待的场合,Python多线程还是发挥了作用,当然如果多线程都是用于CPU密集的代码,那多线程的执行效率就明显会比单线程的低。

  • 程序, 线程与 进程关系

    • 一个程序可能有多个多个进程, 某一个进程里面可以包含多个线程.

线程

创建线程

  • 如何实现: 使用threading 模块

  • 创建一个简单的线程

  • 启动一个线程就是把一个函数传入并创建Thread实例,然后调用start()开始执行

    启动一个线程

    #!/usr/bin/env python
    #-*-coding:utf-8-*-
    import time, threading # 线程执行的代码:
    def thread_demo():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n < 5:
    n = n + 1
    print('thread %s >>> %s' % (threading.current_thread().name, n))
    time.sleep(1)
    print('thread %s ended.' % threading.current_thread().name) if __name__ == "__main__":
    print('thread %s is running...' % threading.current_thread().name)
    t = threading.Thread(target=thread_demo, name= "subThread")
    t.start()
    t.join()
    print('thread %s ended.' % threading.current_thread().name)
    • threading 模块可以直接用来创建一个线程.
    • t. start() 用来启动线程
    • t.join 使多线程按顺序执行
    • threading.Thread实例化线程类.每实例化一个类,相当于开启一个线程, 参数target传递函数, name用来定义子线程的名字.

    启动多个线程

    #!/usr/bin/env python
    #-*-coding:utf-8-*- import threading
    import time def foo(num):
    for i in range(num):
    time.sleep(0.5)
    print i if __name__ == "__main__":
    for x in range(3): # 启动多个线程
    t=threading.Thread(target=foo,args=(3,))
    t.setDaemon(False)
    t.start()
    # 通过join方法让线程逐条执行
    # t.join() #output
    0
    00
    11
    1
    1
    2
    22
    3
    3
    3
    • 其实这就是三个线程并行运行同时输出,所以把结果都输出到一起引起。正是这种乱才整明白了确实三个函数在同时运行。
    • 如果想让结果看起来规则一些可以考虑使用join()方法,join()可以理解为, 函数是按顺序执行的. 但是有时候这并不是我们想要的. 虽然创建了多个线程,没有了并行还要多线程干嘛, 因此join方法不能随便乱用的

线程之Join方法正确姿势

  • 先看代码

    #!/usr/bin/env python
    #-*-coding:utf-8-*- import threading
    import time def foo(num):
    for i in range(num):
    time.sleep(0.5)
    print(i) if __name__ == "__main__":
    """
    创建一个列表,用于存储要启动多线程的实例
    """
    print("MainThread is running....")
    threads = []
    for x in range(3):
    t = threading.Thread(target=foo, args=(3,))
    # 把多线程的实例追加入列表,要启动几个线程就追加几个实例
    threads.append(t) for thr in threads:
    # 把列表中的实例遍历出来后,调用start()方法以线程启动运行
    thr.start() for thr in threads:
    """
    isAlive()方法可以返回True或False,用来判断是否还有没有运行结束
    的线程。如果有的话就让主线程等待线程结束之后最后再结束。
    """
    if thr.isAlive():
    thr.join()
    print("MainThread over")
    • t.setDaemon()方法的时候我们知道,主线程相当于程序的主运行流程。程序运行的时候最先启动的一定就是主线程,主线程负责拉起子线程用于干活。例子中运行函数foo()线程其实都是子线程。因此可以说多线程其实就是多个子线程。那么程序运行完最后一个退出的也肯定就是主线程。因此上例中最后再遍历一遍threads列表的目的就是查看还是否有没有退出的子线程,只要还有子线程是活的,没有退出。通过join()方法强制程序流程不可以走到主线程退出的那个步骤。只有等子线程都退出之后,才能根据join()方法的规则顺序执行到主线程退出的步骤。即最后 输出 MainThread over

线程锁(Lock)

  • 多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

  • 一个混乱的例子

    #!/usr/bin/env python
    #-*-coding:utf-8-*-
    import time
    import threading # 假定这是你的银行存款:
    balance = 0 # 余额 def change_it(m):
    '''正常情况下: 存多少,取多少,金额应该不变'''
    global balance
    balance = balance + m
    balance = balance - m def run_thread(n):
    for i in range(100000):
    change_it(n) if __name__ == "_mian__":
    t1 = threading.Thread(target=run_thread, args=(3,))
    t2 = threading.Thread(target=run_thread, args=(4,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(balance)
    • 定义了一个共享变量balance,初始值为0,并且启动两个线程,先存后取,理论上结果应该为0,但是,由于线程的调度是由操作系统决定的,当t1、t2交替执行时,只要循环次数足够多,balance的结果就不一定是0了。

      原因是因为高级语言的一条语句在CPU执行时是若干条语句 ,即使一个简单的计算:

      balance = balance + x

      也分两步:可看以下两步

      x = balance + n
      balance = x

      数据错误的原因:是因为修改 balance

      需要多条语句,而执行这几条语句时,线程可能中断,从而导致多个线程把同一个对象的内容改乱了。

      两个线程同时一存一取,就可能导致余额不对,你肯定不希望你的银行存款莫名其妙地变成了负数,所以,我们必须确保一个线程在修改balance的时候,别的线程一定不能改。

    • 如果我们要确保balance计算正确,就要给change_it()上一把锁,当某个线程开始执行change_it()时,我们说,该线程因为获得了锁,因此其他线程不能同时执行change_it(),只能等待,直到锁被释放后,获得该锁以后才能改。由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。创建一个锁就是通过threading.Lock()来实现:

  • 为线程上一把锁

    #!/usr/bin/env python
    #-*-coding:utf-8-*- import time
    import threading # 假定这是你的银行存款:
    balance = 0 # 余额 lock = threading.Lock() def change_it(m):
    '''正常情况下: 存多少,取多少,金额应该不变'''
    global balance
    balance = balance + m
    balance = balance - m def run_thread(n):
    for i in range(100000):
    lock.acquire()
    try:
    # 放心地改吧:
    change_it(n)
    finally:
    # 改完了一定要释放锁:
    lock.release() if __name__ == "_mian__":
    t1 = threading.Thread(target=run_thread, args=(3,))
    t2 = threading.Thread(target=run_thread, args=(4,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(balance)
    • 当多个线程同时执行lock.acquire()时,只有一个线程能成功地获取锁,然后继续执行代码,其他线程就继续等待直到获得锁为止。
    • 获得锁的线程用完后一定要释放锁,否则那些苦苦等待锁的线程将永远等待下去,成为死线程。所以我们用try...finally来确保锁一定会被释放。
    • 好处: 确保了某段关键代码只能由一个线程从头到尾完整地执行,比如多线程操作数据库
    • 坏处:首先是阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了; 其次,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁,导致多个线程全部挂起,既不能执行,也无法结束,只能靠操作系统强制终止。

总结

  • 如何创建一个线程?

    • 使用 threading.Thread , 参数包含 target, args, name 等
  • 如何创建多线程?
    • 使用 for 语句
  • 什么是守护线程, 什么是主线程?
    • t.setDaemon(True) 表示的是后台线程, 表示程序流程(主线程)跑完之后直接就关闭了,然后退出了,根本不管子线程是否执行完
    • 默认t.setDaemon(False), 表示前台线程,主线程执行过程中,子线程也在进行,主线程执行完毕后,等待子线程都执行完成后,程序才会停止.
  • join() 方法 使用正确姿势: 使用线程池, 谨慎使用
  • 线程锁: threading.Lock(),解决线程间共享内存,同时对一个变量进行修改时造成数据混乱, 应用有: 比如多个多个线程对数据库同一个数据进行修改

参考

python 并发执行之多线程

Python3 多进程和多线程

python线程入门的更多相关文章

  1. Python 爬虫入门(二)——爬取妹子图

    Python 爬虫入门 听说你写代码没动力?本文就给你动力,爬取妹子图.如果这也没动力那就没救了. GitHub 地址: https://github.com/injetlee/Python/blob ...

  2. Python 爬虫入门之爬取妹子图

    Python 爬虫入门之爬取妹子图 来源:李英杰  链接: https://segmentfault.com/a/1190000015798452 听说你写代码没动力?本文就给你动力,爬取妹子图.如果 ...

  3. 2019年大牛最新整理的Python技术入门路线

    Python作为一门学习上手快.开发效率高.代码优雅的编程语言,一直以来都是最热门的几种语言之一,甚至在进入2019年之后热度超过了十几年的霸主Java,成为最受欢迎的语言.Python一直有胶水语言 ...

  4. 《python开发技术详解》|百度网盘免费下载|Python开发入门篇

    <python开发技术详解>|百度网盘免费下载|Python开发入门篇 提取码:2sby  内容简介 Python是目前最流行的动态脚本语言之一.本书共27章,由浅入深.全面系统地介绍了利 ...

  5. 【Python从入门到精通】(二十五)Python多进程的使用

    您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦. 本篇重点介绍Python多进程的使用,读者朋友们可以将多进程和多线程两者做一个对比学习. 干货满满,建议收藏,需要用到时常看看. 小伙伴们如有问题 ...

  6. Python 正则表达式入门(中级篇)

    Python 正则表达式入门(中级篇) 初级篇链接:http://www.cnblogs.com/chuxiuhong/p/5885073.html 上一篇我们说在这一篇里,我们会介绍子表达式,向前向 ...

  7. Python 正则表达式入门(初级篇)

    Python 正则表达式入门(初级篇) 本文主要为没有使用正则表达式经验的新手入门所写. 转载请写明出处 引子 首先说 正则表达式是什么? 正则表达式,又称正规表示式.正规表示法.正规表达式.规则表达 ...

  8. Python爬虫入门一之综述

    大家好哈,最近博主在学习Python,学习期间也遇到一些问题,获得了一些经验,在此将自己的学习系统地整理下来,如果大家有兴趣学习爬虫的话,可以将这些文章作为参考,也欢迎大家一共分享学习经验. Pyth ...

  9. python——线程与多线程进阶

    之前我们已经学会如何在代码块中创建新的线程去执行我们要同步执行的多个任务,但是线程的世界远不止如此.接下来,我们要介绍的是整个threading模块.threading基于Java的线程模型设计.锁( ...

随机推荐

  1. 挂载Linux云主机硬盘到本地计算机

      现在移动硬盘已经是每个人的生活必需品了,当然网络也是我们生活的必需品,我们现在就是要用网络存储代替硬盘存储,当然再实际使用过程中需要考虑到以下两个问题: 网络延迟 云主机磁盘IO   以上两个关键 ...

  2. CryptoJS与C#AES加解密互转

    CryptoJS下载地址: https://code.google.com/archive/p/crypto-js/downloads http://download.csdn.net/detail/ ...

  3. SPOJ Substrings

    题目链接:戳我 题目大意:给定一个字符串,它的长度n<=2e5.求长度1~n的子串出现的最大次数. 对于一个子串,它的出现次数是多少?就是它所在endpos集合的大小qwq(注意,这里的大小不指 ...

  4. sublime text 另一种对齐

    效果如下: http://sublime-text-unofficial-documentation.readthedocs.org/en/latest/extensibility/plugins.h ...

  5. 【转】POJ分类很好很有层次感

    OJ上的一些水题(可用来练手和增加自信) (poj3299,poj2159,poj2739,poj1083,poj2262,poj1503,poj3006,poj2255,poj3094) 初期: 一 ...

  6. RabbitMQ交换机规则实例

    RabbitMQ Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct.fanout.topic.headers .headers 匹配 AMQP 消息的 header ...

  7. Android多媒体整体架构图

    Android多媒体整体架构图 MediaPlayer框架图 Camera框架图 SoundRecorder框架图 VideoCamera框架图 OpenCore与Skia ALSA Audio框架图 ...

  8. Java零基础教程(一)环境搭建

    本文将带领您一步一步地搭建Java开发环境 一.认识什么是Java Java 是由Sun Microsystems公司于1995年5月推出的高级程序设计语言. Java可运行于多个平台,如Window ...

  9. day 55 linux 的常用命令

    前言 前面咱们已经成功安装了Linux系统--centos7,那么我们现在提好裤腰带,准备奔向Linux的大门.  Linux命令行的组成结构 [root@oldboy_python ~]# [roo ...

  10. pg_stat_statements跳过的坑

    pg_stat_statements跳过的坑 原本以为只是一个简单的插件扩展安装,三下五除二就能搞定,结果搞了很久也没找到问题所在.首先pg_stat_statements已经安装成功,且已经能够使用 ...