个人独立博客:www.limiao.tech
微信公众号:TechBoard


线程的概念

线程就是在程序运行过程中,执行程序代码的一个分支,每个运行的程序至少都有一个线程

单线程执行
import timedef sing():for i in range(3):print("唱歌...%d" % i)time.sleep(1)def dance():for i in range(3):print("跳舞...%d" % i)time.sleep(1)if __name__ == '__main__':sing()dance()
运行结果:唱歌...0
唱歌...1
唱歌...2
跳舞...0
跳舞...1
跳舞...2***Repl Closed***
多线程执行

多线程的执行需要导入threading模块

参数说明:

Thread([group[,target[,name[,args[,kwargs]]]]])
- group: 线程组,目前只能使用None
- target: 执行的目标任务名
- args: 以元组的方式给执行任务传参
- kwargs: 以字典方式给执行任务传参
- name: 线程名,一般不用设置

多线程完成多任务

# 多线程执行
import time, threadingdef sing():# 获取当前进程print(threading.current_thread())for i in range(3):print("唱歌...%d" % i)time.sleep(1)def dance():print(threading.current_thread())for i in range(3):print("跳舞...%d" % i)time.sleep(1)if __name__ == '__main__':sing_thread = threading.Thread(target=sing)dance_thread = threading.Thread(target=dance)sing_thread.start()dance_thread.start()
运行结果:<Thread(Thread-1, started 8520)>
唱歌...0
<Thread(Thread-2, started 4604)>
跳舞...0
唱歌...1
跳舞...1
唱歌...2
跳舞...2***Repl Closed***
多线程执行带有参数的任务
import time, threadingdef sing(num):for i in range(num):print("唱歌...%d" % i)time.sleep(1)def dance(num):for i in range(num):print("跳舞...%d" % i)time.sleep(1)if __name__ == '__main__':sing_thread = threading.Thread(target=sing, args=(3,))dance_thread = threading.Thread(target=dance, kwargs={"num": 3})sing_thread.start()dance_thread.start()
运行结果:唱歌...0
跳舞...0
跳舞...1
唱歌...1
跳舞...2
唱歌...2***Repl Closed***
查看获取线程列表
import time, threadingdef sing():for i in range(5):print("唱歌...%d" % i)time.sleep(1)def dance():for i in range(5):print("跳舞...%d" % i)time.sleep(1)if __name__ == '__main__':# 获取当前程序活动线程的列表thread_list = threading.enumerate()print("111:", thread_list, len(thread_list))sing_thread = threading.Thread(target=sing)dance_thread = threading.Thread(target=dance)thread_list = threading.enumerate()print("222:", thread_list, len(thread_list))# 启动线程sing_thread.start()dance_thread.start()# 只有线程启动了,才能加入到活动线程列表中thread_list = threading.enumerate()print("333:", thread_list, len(thread_list))
运行结果:111: [<_MainThread(MainThread, started 11864)>] 1
222: [<_MainThread(MainThread, started 11864)>] 1
唱歌...0
跳舞...0
333: [<_MainThread(MainThread, started 11864)>, <Thread(Thread-1, started 892)>, <Thread(Thread-2, started 6444)>] 3
跳舞...1
唱歌...1
跳舞...2
唱歌...2
唱歌...3
跳舞...3
唱歌...4
跳舞...4***Repl Closed***
注意

线程之间执行是无序的

import time, threadingdef task():time.sleep(1)print("当前线程:", threading.current_thread().name)if __name__ == '__main__':for _ in range(5):sub_thread = threading.Thread(target=task)sub_thread.start()
运行结果:当前线程: Thread-5
当前线程: Thread-2
当前线程: Thread-3
当前线程: Thread-1
当前线程: Thread-4***Repl Closed***

主线程会等待所有的子线程结束后才结束

# 主线程会等待所有的子线程结束后才会结束
import time, threading# 测试主线程是否会等待子线程执行完成以后程序再退出
def show_info():for i in range(5):print("test:", i)time.sleep(1)if __name__ == '__main__':sub_thread = threading.Thread(target=show_info)sub_thread.start()# 主线程延时5秒time.sleep(10)print("over")
运行结果:test: 0
test: 1
test: 2
test: 3
test: 4
over***Repl Closed***

守护主线程

import time, threadingdef show_info():for i in range(5):print("test:", i)time.sleep(1)if __name__ == '__main__':# 设置成守护主线程,主线程退出后子线程直接销毁不再执行子线程的代码sub_thread = threading.Thread(target=show_info, daemon=True)sub_thread.start()time.sleep(10)print("over")
运行结果:test: 0
test: 1
test: 2
test: 3
test: 4
over***Repl Closed***
自定义线程
import threading# 自定义线程类
class MyThread(threading.Thread):# 通过构造方法取接受任务的参数def __init__(self, info1, info2):# 调用父类的构造方法super().__init__()self.info1 = info1self.info2 = info2# 定义自定义线程相关的任务def test1(self):print(self.info1)def test2(self):print(self.info2)# 通过run方法执行相关任务def run(self):self.test1()self.test2()# 创建自定义线程
my_thread = MyThread("测试1", "测试2")# 启动
my_thread.start()
运行结果:测试1
测试2***Repl Closed***

总结:

  • 自定义线程不能指定target,因为自定义线程里面的任务都统一在run方法里面执行

  • 启动线程统一调用start方法,不要直接调用run方法,因为这样不是使用子线程去执行任务

多线程共享全局变量
import time, threading# 定义全局变量
my_list = list()# 写入数据任务
def write_data():for i in range(5):my_list.append(i)time.sleep(1)print("write_data:", my_list)# 读取数据任务
def read_data():print("read_data:", my_list)if __name__ == '__main__':# 创建写入数据的线程write_thread = threading.Thread(target=write_data)# 创建读取数据的线程read_thread = threading.Thread(target=read_data)write_thread.start()# 主线程等待写入线程执行完成以后代码再继续往下执行write_thread.join()print("开始读取数据...")read_thread.start()
运行结果:write_data: [0, 1, 2, 3, 4]
开始读取数据...
read_data: [0, 1, 2, 3, 4]***Repl Closed***
多线程同时对全局变量进行操作,导致数据可能出现错误
import threading# 定义全局变量
g_num = 0# 循环一次给全局变量加1
def sum_num1():for i in range(1000000):global g_numg_num += 1print("sum1:", g_num)# 循环一次给全局变量加1
def sum_num2():for i in range(1000000):global g_numg_num += 1print("sum2:", g_num)if __name__ == '__main__':# 创建两个线程first_thread = threading.Thread(target=sum_num1)second_thread = threading.Thread(target=sum_num2)first_thread.start()second_thread.start()
运行结果:sum1: 1491056
sum2: 1528560***Repl Closed***

通过上面运行结果,得出:多线程同时对全局变量操作数据发生了错误

原因分析:两个线程first_thread和second_thread都要对全局变量g_num(默认是0)进行加1运算,但是由于是多线程同时操作,,有可能出现下面的情况:

1.在g_num=0时,first_thread取得g_num=0.此时系统把first_thread调度为"sleeping"状态,把second_thread转换为"running"状态,t2也获得g_num=0

2.然后second_thread对得到的值进行加1并赋给g_num,使得g_num=1

3.然后系统又把second_thread调度为"sleeping",把first_thread转为"running".线程t1又把之前得到的0加1后赋值给g_num.

4.这样导致虽然first_thread和second_thread都对g_num加1,但结果仍然是g_num=1。

全局变量数据错误的解决办法

线程同步:保证同一时刻只能有一个线程去操作全局变量同步,就是协同步调,按预定的先后次序进行运行

线程同步的方式:

1.线程等待(join)

2.互斥锁

线程等待实现方式:

import threading# 定义全局变量
g_num = 0# 循环一次给全局变量加1
def sum_num1():for i in range(1000000):global g_numg_num += 1print("sum1:", g_num)# 循环一次给全局变量加1
def sum_num2():for i in range(1000000):global g_numg_num += 1print("sum2:", g_num)if __name__ == '__main__':# 创建两个线程first_thread = threading.Thread(target=sum_num1)second_thread = threading.Thread(target=sum_num2)first_thread.start()first_thread.join()second_thread.start()
运行结果:sum1: 1000000
sum2: 2000000***Repl Closed***

结论:多个线程同时对同一个全局变量进行操作,会有可能出现资源竞争数据错误的问题

线程同步方式可以解决资源竞争数据错误问题,但是这样有多任务变成了单任务

互斥锁

对共享数据进行锁定,保证同一时刻只能有一个线程去操作

抢到锁的线程先执行,没有抢到锁的线程需要等待,等锁用完后需要释放,然后其它等待的线程再去抢这个锁,哪个线程抢到,那个线程再执行

具体哪个线程抢到这个锁,我们决定不了,是由CPU调度决定的

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁

互斥锁为资源引入的一个状态:锁定/非锁定

某个线程要更改共享数据时,先将其锁定,此时资源的状态为"锁定",其他线程不能更改;直到该线程释放资源,将资源的状态变成"非锁定",其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。

创建锁:

a = threading.Lock()

锁定

a.acquire()

释放

a.release()

注意:

1.如果这个锁之前时没有上锁的,那么acquire不会堵塞

2.如果在调用acquire对这个锁上锁之前,它已经被其它线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止

# 使用互斥锁完成2个线程对同一个全局变量各加100万次的操作
import threading# 定义全局变量
g_num = 0# 创建全局互斥锁
lock = threading.Lock()# 循环一次给全局变量加1
def sum_num1():# 上锁lock.acquire()for i in range(1000000):global g_numg_num += 1print("sun1:", g_num)# 释放锁lock.release()# 循环一次给全局变量加1
def sum_num2():# 上锁lock.acquire()for i in range(1000000):global g_numg_num += 1print("sum2:", g_num)# 释放锁lock.release()if __name__ == '__main__':# 创建线程first_thread = threading.Thread(target=sum_num1)second_thread = threading.Thread(target=sum_num2)# 启动线程first_thread.start()second_thread.start()
运行结果:sun1: 1000000
sum2: 2000000***Repl Closed***

注意

加上互斥锁,哪个线程抢到这个锁我们决定不了,哪个线程抢到锁哪个线程先执行,没有抢到的线程需要等待

加上互斥锁多任务瞬间变成单任务,性能会下降,也就是说同一时刻只能有一个线程去执行

使用互斥锁的目的

能够保证多个线程访问共享数据不会出现资源竞争及数据错误

上锁、解锁过程

当一个线程调用锁的acquire()方法获得锁时,锁就进去了"locked"状态。

每次只有一个而线程可以获得锁,如果此时另一个线程试图获得这个锁,该线程就会变为"blocked"状态,称为"阻塞",直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入"unlocked"状态。

线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行"running"状态

死锁

一直等待对方释放锁的情景就是死锁

根据下标在列表中取值,但是要保证同一时刻只能有一个线程去取值

# 死锁示例:
import time, threading# 创建互斥锁
lock = threading.Lock()def get_value(index):# 上锁lock.acquire()print(threading.current_thread().name)my_list = [3, 6, 8, 1]# 判断下标释放越界if index >= len(my_list):print("下标越界:", index)returnvalue = my_list[index]print(value)time.sleep(1)# 释放锁lock.release()if __name__ == '__main__':# 模拟大量线程去执行取值操作for i in range(30):sub_thread = threading.Thread(target=get_value, args=(i,))sub_thread.start()

避免死锁:

# 死锁示例:
import time, threading# 创建互斥锁
lock = threading.Lock()def get_value(index):# 上锁lock.acquire()print(threading.current_thread().name)my_list = [3, 6, 8, 1]# 判断下标释放越界if index >= len(my_list):print("下标越界:", index)lock.release()returnvalue = my_list[index]print(value)time.sleep(1)# 释放锁lock.release()if __name__ == '__main__':# 模拟大量线程去执行取值操作for i in range(30):sub_thread = threading.Thread(target=get_value, args=(i,))sub_thread.start()

小结:使用互斥锁的时候需要注意死锁的问题,要在合适的地方注意释放锁

死锁一旦发生就会造成应用的停止响应


个人独立博客:www.limiao.tech
微信公众号:TechBoard


Python网络编程 —— 线程相关推荐

  1. python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)...

    python网络编程基础(线程与进程.并行与并发.同步与异步.阻塞与非阻塞.CPU密集型与IO密集型) 目录 线程与进程并行与并发同步与异步阻塞与非阻塞CPU密集型与IO密集型 线程与进程 进程 前言 ...

  2. python网络编程(苦肝一夜,近万字)

    文章目录 一.TCP/IP简介 二.网络设计模块 1.Socket简介 2.python中的socket模块,使用该模块建立服务器需要6个步骤. 1.创建socket对象. 2.将socket绑定(指 ...

  3. Python 网络编程(Socket)

    Python 网络编程(Socket) 一.Socket 套接字 1.Socket 编程 socket本质是编程接口(API),对TCP/IP的封装,提供可供程序员做网络开发所用的接口.Socket ...

  4. python网络编程知识点_python 网络编程要点

    From http://www.zhihu.com/question/19854853 Python网络编程是一个很大的范畴,个人感觉需要掌握的点有: 1. 如何使用Python来创建socket, ...

  5. python网络编程需要学什么,python网络编程学习笔记(五):socket的一些补充 Python 网络编程需要学习哪些网络相关的知识...

    python的socket编程问题hdr = recvall(s, 5) if hdr is None: print 'Unexpected EOF receivingstruct在unpack的时候 ...

  6. python网络编程内容_图解Python网络编程

    Python Python开发 Python语言 图解Python网络编程 本篇索引 (1)基本原理 本篇指的网络编程,仅仅是指如何在两台或多台计算机之间,通过网络收发数据包:而不涉及具体的应用层功能 ...

  7. 读书笔记 - -《Python网络编程》重点

    文章目录 一.前言 二.客户/服务器网络编程简介 三.UDP 3.1 端口号 3.2 套接字 3.3 UDP分组 3.4 小结 四.TCP 4.1 TCP工作原理 4.2 绑定接口 4.3 死锁 4. ...

  8. Python网络编程基础之ip地址,端口号,TCP,socket

    Python网络编程基础 IP地址 ip地址表现形式 查看ip地址 Linux Windows 检查网络是否正常 端口与端口号 端口号分类 知名端口号 动态端口号 TCP协议 概念 TCP通讯步骤 特 ...

  9. python网络编程难点_python之路网络编程总结(三)

    2018-9-22 20:58:25 1. 端口 1.1知名端口是众所周知的端口,范围从0-1023 例: 80端口分配给HTTP服务 21 端口分配给FTP服务 1.2动态端口 : 范围从1024 ...

  10. python网络编程基础百度云_PYTHON网络编程基础 PDF 下载

    相关截图: 资料简介: <Python网络编程基础>全面介绍了使用Python语言进行网络编程的基础知识,主要内容包括网络基础知识.高级网络操作.Web Services.解析HTML和X ...

最新文章

  1. 不懂技术系列--如何快速调试html5页面/手机页面
  2. 高级数据结构 线段树
  3. windows 中 Eclipse 打开当前文件所在文件夹
  4. git merge最简洁用法
  5. Dataset之Knifey-Spoony:Knifey-Spoony数据集的简介、下载、使用方法之详细攻略
  6. 卡在登陆界面进不去_穿越火线第十三年:需要的不仅仅是新界面,重要还是留住旧玩家...
  7. Rails当你运行一个数据库回滚错误:ActiveRecord::IrreversibleMigration exception
  8. qunit 前端脚本测试用例
  9. 点评老师freeeim
  10. Linux socket can例程python版本
  11. Tiktok预计下半年开通购物车,你有想法做吗?
  12. php 判断是否为字符串,php怎么判断是不是字符串
  13. 阿里云服务器一年多少钱?最便宜的一年
  14. 【活动推荐】美团外卖两千万日订单背后的客户端技术架构
  15. 如何在web端登录企业邮箱? 163企业邮箱怎么登陆?
  16. 数据库搭建范式——BC范式
  17. 可变悬挂调节软硬_可变悬架软硬怎么调节高度
  18. 网络综合布线测试的新选择-AEM
  19. simulink 报错Derivative of state ‘1‘ in block ..... at time 0.0 is not finite.
  20. 如何使流水号条码不重复打印

热门文章

  1. cd40系列芯片_IC集成电路型号大全及40系列芯片功能大全
  2. html5画电池状态,JavaScript里获取电池状态的方法
  3. android shell强制删除文件夹_【代码合集】VBA操作文件夹代码合集
  4. linux查看cpt硬盘命令,Linux基础知识复习之命令篇
  5. set python_set在python里的含义和用法
  6. html判断sql没结果,SQL存储过程测试(8)——当待测存储过程没有返回值的时候 如何判断测试结果是否通过...
  7. golang1.1-基础环境的配置以及事项
  8. python iotextwrapp执行不动_Python tkinter - 第10章 文本控件(Text)方法
  9. 服务器入站规则 共享文件,How to :发布内部网络中的文件共享服务
  10. 导出文件后打不开_微信收到CAD图纸打不开?只要有这个神器,手机即可1秒轻松打开...