博主前言:

通过第一篇文章的学习,读者已经认识了网络编程中的套接字编程,已经具备了实现基于TCP协议和基于UDP协议网络编程中客户端的实现。第二篇文章打算让读者感受一下多线程的魅力,通过仔细阅读本篇文章完全可达到一文入门多线程的目的。

1. 几个基本概念

1.1 单核CPU与多核CPU

CPU(central processing unit),即中央处理器,是作为计算机系统的运算和控制核心,是信息处理、程序运行的最终执行单元。
所谓单核CPU就是指同一时刻计算机只能执行一件事情。即电脑在同一时刻只运行一种程序。
那么多核CPU就是指同一时刻计算机可以执行多件事情。即电脑在同一时刻可执行多种程序。

1.2 并行与并发

我相信读到这儿读者肯定是有疑问的,为什么在配置单核CPU的计算机上既可以听音乐,同时又可以打开QQ聊天,同时还可以挂着游戏呢?
这一切的原因都是因为CPU运行的速度实在是太快了,一秒钟可以执行上百万次。在单核CPU的计算机上,我们先把音乐播放的软件拿来执行,让它执行0.0001s,然后再把QQ的程序拿来执行0.0001s,最后再把游戏的程序拿来执行0.0001s,循环往复。虽然我们执行的时间只有0.0001s,但是在CPU眼里,这个时间已经足够长了,所以达到了我们视觉上的效果,好像音乐、QQ、游戏同时在运行的样子。类似于现实生活中的翻书动画一般,如下图所示。
这是一种伪多任务的情况,对于CPU这种雨露均沾的操作,我们有一个名字叫做:时间片轮转
同时,对于这种假的多任务,我们称之为并发
那么是否存在真的多任务呢?
答案是肯定的,多核CPU就可以实现真的多任务。当三个程序在配置四核CPU的计算机上运行的时候,就可以称之为真的多任务。我们将这种真正的多任务称之为并行
但是在实际的生活中,我们在电脑上运行的程序数量往往大于CPU的核数,也就是说,我们在现实生活中称的多任务,99%指的都是并发,而不是并行。

2. 多线程

线程是实现多任务的一种手段。通俗来讲,一个程序运行起来之后,一定有一个执行代码流程的次序,这个次序,就称之为线程。

2.1 单线程

单线程指的是在程序执行时,所走的程序路径按照连续顺序排下来,前面的必须处理好,后面的才会执行。
用一句话来形容单线程就是:单线程就是一心一意,用情专一的痴情少年。
我们通过一段代码来感受一下单线程。

# 引入time模块,定时使用
import time
# 定义唱跳rap篮球的函数
def sing():for i in range(3):           # 用for循环来模拟实现print("我是蔡徐坤,我会唱。")time.sleep(1)def dance():for i in range(3):print("我是蔡徐坤,我会跳。")time.sleep(1)def rap():for i in range(3):print("我是蔡徐坤,我会rap。")time.sleep(1)def basketball():for i in range(3):print("我是蔡徐坤,我会篮球。")time.sleep(1)
# 定义主函数
def main():     sing()dance()rap()basketball()if __name__ == '__main__':main()

通过执行上述代码,运行结果如下图,我们可以看到一个会唱跳rap篮球的蔡徐坤,但是这个蔡徐坤同一时刻只能干一件事情,唱的时候不会跳,跳的时候不会rap,rap的时候不能篮球。

这就是一个单线程的程序代码,在执行dance函数之前,sing函数一定要执行完整,代码的执行按照规定的连续顺序依次执行,前面处理好,后面才能执行。那么,我们要怎样才能使多个函数“一起”执行呢?‘

2.2 多线程

要让多个函数“一起”执行,就要使用多线程了。
这里我们学习到一个新的模块:threading模块
我们修改一下上述代码,将其多线程化。

import threading         # 引入threading模块
import timedef sing():for i in range(3):print("我是蔡徐坤,我会唱。")time.sleep(1)def dance():for i in range(3):print("我是蔡徐坤,我会跳。")time.sleep(1)def rap():for i in range(3):print("我是蔡徐坤,我会rap。")time.sleep(1)def basketball():for i in range(3):print("我是蔡徐坤,我会篮球。")time.sleep(1)def main():test1 = threading.Thread(target=sing)  #创建一个Tread对象test2 = threading.Thread(target=dance)  #创建一个Tread对象test3 = threading.Thread(target=rap)  #创建一个Tread对象test4 = threading.Thread(target=basketball)  #创建一个Tread对象test1.start()  #创建子线程test2.start()  #创建子线程test3.start()  #创建子线程test4.start()  #创建子线程if __name__ == '__main__':main()

通过运行上述代码,我们可以清楚的认识到多线程和单线程的区别,不禁在心中大喊一声“多线程牛逼”。
实际上,多线程的使用更加广泛和深刻,结合我们上篇文章学习的套接字编程的内容,我们已经具备了编写实现一个基于UDP协议或TCP协议聊天室功能的小程序的能力,希望读者们可以实践实践。

2.3 主线程和子线程

在threading模块中,有一个enumerate的函数,此函数返回类型为列表类型,列表元素是线程的内容,元素的个数是线程的数量。我们可以通过enumerate函数证明,当调用Thread类的构造方法创建对象时,子线程还没有被创建,只有当该对象调用start方法时,子线程才被创建。

import threading
import timedef test():for i in range(3):print("This is a test")time.sleep(1)def main():print(threading.enumerate())   # 调用enumerate函数,查看在创建Thread对象之前的当前线程test1 = threading.Thread(target=test)print(threading.enumerate())  # 调用enumerate函数,查看在创建Thread对象之后的当前线程test1.start()print(threading.enumerate())    # 调用enumerate函数,查看Thread对象开始之后的当前线程if __name__ == '__main__':main()


我们在Thread对象创建的前、后,以及Thread对象调用开始函数之后,分别调用enumerate函数,查看当时线程情况,可得到以上图示情况。
此结果说明,我们在创建Thread对象的时候没有创建子线程,只有在Thread对象调用start函数时,子线程才被创建,并开始执行其相应代码。而且只有当子线程的代码运行结束之后,主线程才结束运行。

3. 共享全局变量

全局变量,即定义在函数外部的变量。每一个实例函数都可以对全局变量进行使用,我们想象一个情况,当子线程1和子线程2同时对一个全局变量进行使用修改的时候,其结果究竟会是怎样呢?

import threading
# 定义一个全局变量a,赋值为0
a = 0
def test1():# 修改全局变量a的值global afor i in range(100):a = a+1print("经过test1后a的值为:",a)def test2():# 修改全局变量a的值global afor i in range(100):a = a+1print("经过test2后a的值为:",a)def main():t1 = threading.Thread(target=test1)t2 = threading.Thread(target=test2)t1.start()     # 创建子线程1t2.start()      # 创建子线程2if __name__ == '__main__':main()


上段代码创建了一个子线程1和子线程2,两个线程同时对全局变量a进行加1操作,所示结果如上图。结果说明,当两个线程同时对全局变量进行使用的时候,可以共享使用。可,当我们将循环次数加到一百万时,运行结果又会如何呢?

当我们把循环次数加到足够大的时候,结果将会让我们大吃一惊!这是为什么呢?
这是因为我们在使用全局变量的时候,系统会解析成很多句话。

  1. 获取全局变量a的值
  2. 把获取的全局变量a值+1
  3. 把第2步所得的结果存储到全局变量a中

所以,当程序调用全局变量的时候,有可能子线程1执行到第二步的时候,子线程2就开始执行第一步,此时子线程2获取的全局变量a的值为子线程1此次执行调用的全局变量的值相同,因为子线程1此次执行没有调用第三步存储第二步的值。(读者请重点理解这段话)

3.1 互斥锁

为了防止当多个线程几乎同时修改某一个共享数据的时候,造成数据丢失,我们引入了互斥锁,进行同步控制。
互斥锁状态:锁定/非锁定
当某个线程更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成非锁定,其他线程才能再次锁定该资源,互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
我们把互斥锁加入上诉代码:

import threading
# 定义一个全局变量a,赋值为0
a = 0
# 创建互斥锁
mutex = threading.Lock()def test1():global a#锁定mutex.acquire()for i in range(1000000):a = a+1# 释放mutex.release()print("经过test1后a的值为:",a)def test2():global a#锁定mutex.acquire()for i in range(1000000):a = a+1# 释放mutex.release()print("经过test2后a的值为:",a)def main():t1 = threading.Thread(target=test1)t2 = threading.Thread(target=test2)t1.start()t2.start()if __name__ == '__main__':main()

我们调用threading模块的Lock函数创建互斥锁对象,同时将资源使用acquire函数上锁,当执行完毕后,调用release函数释放资源。
若上锁之前没有被上锁,那么此时上锁成功。如果上锁之前已经被上锁了,那么此时会堵塞在这里,直到该资源解锁。

3.2 死锁

在多线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源就会造成死锁。
如何有效避免死锁是开发者在实际开发过程中必须注意的事项。通常避免死锁的方法有

  1. 银行家算法
  2. 添加超时时间

「Python网络编程」如何让蔡徐坤同时唱跳rap篮球/初识多线程(二)相关推荐

  1. 「Python 网络自动化」目录汇总

    目录 Netmiko NETCONF Nornir Paramiko Napalm NetBox TextFSM 其他 关于文章 关于我 Netmiko 「Python 网络自动化」Netmiko - ...

  2. python里的resize_Python玩转蔡徐坤

    很无聊地做了一个视频转字符图的Python代码,把最近火的一趟糊涂的蔡徐坤转成字符图像. 效果可以看这里 https://www.bilibili.com/video/av50295619/​www. ...

  3. 「Python 网络自动化」Nornir—— Inventory(主机清单)介绍

    Nornir 是一个非常好用的网络自动化的框架,最近我输出了一份 Nornir 中文手册,欢迎大家阅读指正. 主机清单 主机清单(Inventory) 是 nornir 最重要的部分,它由 hosts ...

  4. Python代码画喜羊羊怎么画_利用Python让你的命令行像蔡徐坤一样会打篮球

    原理 既然要开始做东西,首要的问题就是想好要怎么做,大家都知道视频是由一系列图片一帧一帧组成的,因此视频转字符动画最基本的便是图片转字符画. 在这里简单的说一下图片转字符画的原理:首先将图片转为灰度图 ...

  5. 计算机弹奏蔡徐坤,用了多年键盘才发现,CTRL键跟蔡徐坤有关,细思极恐!

    原标题:用了多年键盘才发现,CTRL键跟蔡徐坤有关,细思极恐! 自从电脑出现以后,我们就在享受电脑带来的便利.而我们在使用电脑的时候就需要键盘来进行打字,所以我们想要完整的使用电脑键盘是少不了的. 而 ...

  6. python生成器单线程_「Python异步编程-3」协程、生成器、yield 的联系

    异步编程的基础在于理解协程,而协程的基础在于理解生成器,而生成器的基础在于理解yield关键字,下面就来说说这几个概念. 什么是yield关键字? 相当于return关键字,在每次next(),或者f ...

  7. 「雕爷学编程」Arduino动手做(15)——手指侦测心跳模块

    37款传感器和模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器与模块,依照实践出真知(动手试试)的理念,以学习和交流为目的,这里准备 ...

  8. 「面向信仰编程」Draven 专访:像写代码一样,用树形的结构写文章

    本文首发于"Shopee技术团队"公众号 站在网络与技术前沿的不少开发者们,还保持着如今看起来略显古早的 Web 2.0 时代行为--写博客. 技术博客写作是工程师们沿袭已久的传统 ...

  9. 程序猿小哥用12万行代码堆出来个「蔡徐坤」!编译竟然还能通过

    点击上方"Github中文社区",关注 看遍Github好玩的项目 量子位 大家好,我是hub哥! 有个程序猿,用12万行代码堆出来个「蔡徐坤」,编译还能通过?! 它是这样的: 不 ...

  10. 哈哈!12万行代码堆出来个「蔡徐坤」

    鱼羊 白交 发自 凹非寺 量子位 报道 | 公众号 QbitAI 有个程序猿,用12万行代码堆出来个「蔡徐坤」,编译还能通过?! 它是这样的: 不是简单的灰度字符画!也不是控制台控制颜色!而是在唱.跳 ...

最新文章

  1. Linux之make 、makefile的使用方法
  2. Mac OS 下安装 MacVim
  3. java中System重定向输出流
  4. 【Linux开发】linux设备驱动归纳总结(一):内核的相关基础概念
  5. Linux下VNC配置多个桌面和修改密码 不会当系统重启vnc失效
  6. ReentrantLock 的实现原理
  7. jdk卸载不干净怎么办_雨刮器“刮不干净”怎么办?老司机:用这招,分分钟解决!...
  8. sql语句update中多个case/when的写法
  9. jpa in查询_优选在shopee虾皮怎么发货价格查询皮皮虾云仓
  10. oracle查看视图数据,查看oracle 10g 视图-数据库专栏,ORACLE
  11. 笨办法学 Python · 续 练习 21:二分搜索
  12. iOS通过iTunes search检测版本更新,并提示用户更新!
  13. 什么是波导的简并波,矩形波导和圆波导中的简并有何异同
  14. MySQL incompatible with sql_mode=only_full_group_by 问题解决
  15. 淘淘商城第59讲——搭建Solr集群
  16. angular 单击和双击事件分开
  17. 用计算机分析卫星云图 进行实时天气,气象卫星云图在地面气象观测中的运用...
  18. 利用matlab符号变量进行矩阵乘法公式推导
  19. 利用tcp协议实现大文件传输(socket)
  20. 【解决】Failed to process import candidates for configuration class [cn.itcast.eureka.EurekaApplication]

热门文章

  1. 单机塔防游戏推荐_十大塔防单机手游2019 简单好玩的单机塔防游戏推荐
  2. 《内向性格的竞争力:发挥你的本来优势》读书笔记
  3. 自定义图标iconfont
  4. 接口文档系统 - Yapi
  5. 解决IE兼容H5的问题
  6. Caused by: java.lang.IllegalStateException: Process 9461 exceeded cursor quota 100, will kill it
  7. 传统3D游戏引擎的Web化
  8. 【干货】在拉斯维加斯,程序员如何靠bandits算法干掉老虎机
  9. Bada学习-(十一)文件系统
  10. 物种内共线性分析——思路以及踩坑总结(二)