python进阶之 线程编程
1.进程回顾
之前已经了解了操作系统中进程的概念,程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。在多道编程中,我们允许多个程序同时加载到内存中,在操作系统的调度下,可以实现并发地执行。这是这样的设计,大大提高了CPU的利用率。进程的出现让每个用户感觉到自己独享CPU,因此,进程就是为了在CPU上实现多道编程而提出的。
2.有了进程为什么还需要线程?
进程有很多优点,它提供了多道编程,让我们感觉我们每个人都拥有自己的CPU和其他资源,可以提高计算机的利用率。很多人就不理解了,既然进程这么优秀,为什么还要线程呢?其实,仔细观察就会发现进程还是有很多缺陷的,主要体现在两点上: 1.进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。 2.进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行,浪费系统资源。
3.线程的基本概念
线程是进程中的一个单位 进程:计算机中最小的资源分配单位 线程:计算机中被CPU调度的最小单位
4.为什么要使用线程?
1.线程也叫轻量级的进程,最大的特点是同一个进程中的多个子线程是共享一部分数据(一个进程的多个子进程和父进程相互隔离,没有关系)2.线程的创建\销毁\上线文切换要比进程高效
5.进程和线程的区别?
进程:数据隔离(但也可以使用Manager模块实现数据共享),使用开销大 线程:数据共享(全局变量),使用开销小 通过小的细节来体现进程和线程的区别: 1.os.getpid() 进程:父进程和子进程的pid都不同,所以都各自有自己的内存空间 2.if __name_- == '__main__' 进程和线程的创建原理不同,所以不需要if __name__ == '__main__' 因为新的线程是在主线程的内存中,所以新的线程和主线程共享同一段代码,不需要import导入,也就不存在子线程中又重复一次创建线程的过程
在cpython中多进程可以使用多核,但是多线程不能使用多核是cpython解释器的原因造成的,因为cpython存在GIL全局解释器锁,导致同一时间内只能有一个线程访问cpu,保证数据安全,但是jpython和pypy就能访问多核的cpu 正常的多个线程和多个进程可不可以利用多核(多个cpu )? 可以,因为cpu执行的是进程中的线程
解释性语言不能多个线程同时访问多个cpu,这样做能保证数据不出错
6.Python中的线程模块
threading 和 multiprocessing在使用上基本相同 先有的threading模块,没有池的功能 multiprocessing完全模仿threading模块完成的,实现了池的功能 concurrent.futures,实现了线程池\进程池
如何开启一个线程
from threading import Thread import os def func(): print('in fucn ',os.getpid()) print('in main ',os.getpid()) Thread(target=func).start() #两个打印pid是一样的,说明线程是由进程产生的
方法1
from threading import Thread import time class Sayhi(Thread): def __init__(self,name): super().__init__() self.name=name def run(self): time.sleep(2) print('%s say hello' % self.name) if __name__ == '__main__': t = Sayhi('kobe') t.start() print('主线程')
方法2
开启一个多线程
import time from threading import Thread import os def func(i): time.sleep(1) print('in fucn',i,os.getpid()) print('in main',os.getpid()) for i in range(20): #Thread(target=func,args=(i,)).start() #问题:多个线程在同一个进程中处理数据的时候,数据需不需要锁? 当然需要,这就是GIL锁存在的意义:同一进程内的同一时刻只能有一个线程访问cpu来修改同一个数据,导致Python中的多线程是‘伪多线程’,所以cpython解释器不能使用多核
线程的其他方法
Thread实例对象的方法 isAlive(): 返回线程是否活动的。 getName(): 返回线程名。 setName(): 设置线程名。 threading模块提供的一些方法: threading.currentThread(): 返回当前的线程变量。 threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行子线程启动后、结束前,不包括启动前和终止后的线程。 threading.active_count(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果 主线程也是一个线程,每个线程没有被start()的时候们不会被执行 判断主线程是否结束了 线程有terminate么? 没有terminate 不能强制结束 所有的子线程都会在执行完所有的任务之后自动结束 # 返回一个存储着所有线程对象的列表 from threading import enumerate,Thread def func(): print('in son thread') Thread(target=func).start() print(enumerate())
通过dis模块查看机器码
from dis import dis def func(): a =[] a.append(1) dis(func) 注意:cpu在0.9ms内可以处理450w机器码
python解释器 --> 字节码 --> 机器码
7.线程中守护进程
import time from threading import Thread,activeCount #守护线程守护的 def daemon_func(): while True: time.sleep(0.5) print('守护线程') def son_func(): print('start son') time.sleep(10) print('end son') print('子线程:',time.time()-start) start= time.time() #子线程 t = Thread(target=daemon_func) t.daemon = True t.start() #子线程 Thread(target=son_func).start() #主线程 print(activeCount()) time.sleep(3) print('主线程结束') print('主线程:',time.time()-start)
线程中的守护线程
无论是进程还是线程,都遵循:守护xx会等待主xx运行完毕后被销毁。需要强调的是:运行完毕并非终止运行
1.对主进程来说,运行完毕指的是主进程代码运行完毕
2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
如果你设置一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。如果你的主线程在退出的时候,不用等待那些子线程完成,那就设置这些线程的daemon属性。
1.主线程会等待子线程的结束而结束,主线程不会管守护线程的线程是否执行完毕2.守护线程会随着主线程的结束而结束,注意是运行完毕,并非终止运行,守护线程会守护主线程和所有的子线程3.整个Python会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。(主进程会随着主线程的结束而结束)
问题 1.主线程需不需要回收子线程的资源 # 不需要,线程资源属于进程,所以进程结束了,线程的资源自然就被回收了 2.主线程为什么要等待子线程结束之后才结束 # 主线程结束意味着进程进程,进程结束,所有的子线程都会结束,要想让子线程能够顺利执行完,主线程只能等 3.守护线程到底是怎么结束的 # 主线程结束了,主进程也结束,守护线程被主进程的结束(运行完毕)给杀死了
8.思考:有了GIL锁,线程中还需要锁么?
有必要理论上有了GIL锁会保证同一时间内只有一个线程执行cpu指令,但是运行多线程不加锁的话,会产生数据不安全的情况有下列代码: 当tt里面的第一个线程在执行的时候,假如说执行到count =count+1 但是还没来得及把count放回去的时候,cpu就切换到下一个线程执行,此时的count并没有 修改,还是0 ,于是就将coun加1,count=1,在切换到上一个线程继续执行,cpu寄存器里面 存放的上一个线程的状态是要将count+1的结果存回给count,所以在两个线程运行的时候只加了一次 造成count值不对,也就造成了数据不安全的问题。所以要加上锁 这种情况主要是由于是cpu切换线程造成的 ''' coun = 0 def func(): count +=1 for i in range(2): tt = Thread(target=func) tt.start() '''线程中不安全现象: 1.对全局变量进行修改 2.对某个值进行+=,-=,*=,/+,%=等等操作 产生的原因是cpu切换线程造成的 为什么某个值进行+=,-=,*=,/+,%=在cpu切换的时候造成数据不安全的现象? 因为在count +=1等操作,在cpu的机器码中执行是可再分的,分成两步执行,count +1,count=count+1 所以在 cpu切换线程的时候,有可能出现两个线程分别对一个数据进行修改,而返回的时候只是返回一个 但是Python中常用的数据类型,list dict tuple set等修改或增加的方法都具有原子性(不是增加就是修改,就算cpu切换也无所谓) 具有原子性也是在cpu底层机器码中体现出来的 注意: 队列(queue)的原子性比以上的数据类型更强,因为队列的queue.get()获取不到数据的时候回阻塞住,而列表的list.pop()在没有数据时会报错
9.锁
互斥锁:在同一个线程中连续acquire两次,并且可以做到多个线程被锁的代码同时只有一个线程执行,中就是互斥锁,就是死锁 递归锁:在同一个线程中可以连续acquire多次,并且可以做到多个线程被锁的代码同时只有一个线程执行,但是大部分时候递归锁的情况都能使用互斥锁来解决(通常使用递归锁的代码都是有逻辑上问题的) 一般情况互斥锁的性能和时间复杂度要比递归所低,但是递归锁一把锁就可以打天下 递归锁最大的特点是:在同一个线程和进程不会出现死锁现象,并且能连续acquire两次,这是最大的特点
死锁现象;创建了两把锁以上,并且交替使用就可能出现死锁 只要是1把锁,递归锁永远不会死锁 只要是2把锁以及以上,都有可能产生死锁现象 r1 = r2 = RLock() 这是同一把锁 只要是2把锁以上,交替使用,递归锁和互斥锁都会产生死锁
1.gil 保证线程同一时刻只能一个线程访问CPU,不可能有两个线程同时在CPU上执行指令2.lock 锁 保证某一段代码 在没有执行完毕之后,不可能有另一个线程也执
from threading import Lock,Thread import time noodle_lock = Lock() fork_lock = Lock() def eat1(name): noodle_lock.acquire() # 阻塞等面 time.sleep(0.5) print('%s拿到面了'%name) fork_lock.acquire() print('%s拿到叉子了' % name) print('%s吃面'%name) fork_lock.release() print('%s放下叉子了' % name) noodle_lock.release() print('%s放下面了' % name) def eat2(name): fork_lock.acquire() # 阻塞等叉子 print('%s拿到叉子了' % name) noodle_lock.acquire() print('%s拿到面了'%name) print('%s吃面'%name) noodle_lock.release() print('%s放下面了' % name) fork_lock.release() print('%s放下叉子了' % name) Thread(target=eat1,args = ('admin',)).start() Thread(target=eat2,args = ('kobe',)).start() Thread(target=eat1,args = ('jordan',)).start()
死锁
from threading import RLock,Thread fork_lock = noodle_lock = RLock() def eat1(name): noodle_lock.acquire() # 阻塞 宝元等面 print('%s拿到面了'%name) fork_lock.acquire() print('%s拿到叉子了' % name) print('%s吃面'%name) fork_lock.release() print('%s放下叉子了' % name) noodle_lock.release() print('%s放下面了' % name) def eat2(name): fork_lock.acquire() # 阻塞 wusir等叉子 print('%s拿到叉子了' % name) noodle_lock.acquire() print('%s拿到面了'%name) print('%s吃面'%name) noodle_lock.release() print('%s放下面了' % name) fork_lock.release() print('%s放下叉子了' % name) Thread(target=eat1,args = ('alex',)).start() Thread(target=eat2,args = ('wusir',)).start() Thread(target=eat1,args = ('baoyuan',)).start()
递归锁
10.队列
Queue就是一个线程队列的类,自带lock锁,实现了线程安全的数据类型队列是一个线程安全的数据类型,队列在多线程中占有重要的安全位置from queue import Queue:是线程安全的,和进程无关,进程通信是ipc,线程是共享内存的,二者通信方式不同
from queue import Queue #Queue就是一个线程队列的类,自带lock锁,实现了线程安全的数据类型 #队列是一个线程安全的数据类型,只有几个特点的属性不是安全的 q = Queue() # 先进先出队列 # 在多线程下都不准 # q.empty() 判断是否为空 # q.full() 判断是否为满 # q.qsize() 队列的大小 #在多进程下都是数据安全的 q.put({1,2,3}) q.put_nowait('abc') print(q.get_nowait()) print(q.get())
先进先出队列
# 先进后出的队列 last in first out from queue import LifoQueue #线程安全的队列 栈和后进先出的场景都可以用 lfq = LifoQueue() lfq.put(1) lfq.put('abc') lfq.put({'}) print(lfq.get()) print(lfq.get()) print(lfq.get())
后进先出队列
from queue import PriorityQueue # 优先级队列 pq = PriorityQueue() pq.put((10,'askdhiu')) #前面的数字就是优先级,越小越优先 pq.put((2,'asljlg')) pq.put((20,'asljlg')) print(pq.get()) print(pq.get()) print(pq.get())
优先级队列
python进阶之 线程编程的更多相关文章
- Python进阶:函数式编程实例(附代码)
Python进阶:函数式编程实例(附代码) 上篇文章"几个小例子告诉你, 一行Python代码能干哪些事 -- 知乎专栏"中用到了一些列表解析.生成器.map.filter.lam ...
- Python进阶之面向对象编程
面向对象编程——Object Oriented Programming,简称OOP,是一种程序设计思想.OOP把对象作为程序的基本单元,一个对象包含了数据和操作数据的函数. 面向过程的程序设计把计算机 ...
- Python进阶之函数式编程
函数式编程 函数是Python内建支持的一种封装,我们通过把大段代码拆成函数,通过一层一层的函数调用,就可以把复杂任务分解成简单的任务,这种分解可以称之为面向过程的程序设计.函数就是面向过程的程序设计 ...
- Python进阶之函数式编程(把函数作为参数)
什么是函数式编程? 什么是函数式编程? 函数:function 函数式:functional,一种编程范式 函数式编程是一种抽象计算的编程模式 函数≠函数式,比如:计算≠计算机 在计算机当中,计算机硬 ...
- python进阶-------进程线程(二)
Python中的进程线程(二) 一.python中的"锁" 1.GIL锁(全局解释锁) 含义: Python中的线程是操作系统的原生线程,Python虚拟机使用一个全局解释器锁(G ...
- python进阶——进程/线程/协程
1 python线程 python中Threading模块用于提供线程相关的操作,线程是应用程序中执行的最小单元. #!/usr/bin/env python # -*- coding:utf-8 - ...
- python进阶------进程线程(四)
Python中的协程 协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其 ...
- python进阶之 进程编程
1.进程 顾名思义,进程即正在执行的一个过程.进程是对正在运行程序的一个抽象. 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一.操作系统的其他所有内 ...
- [ Python - 14 ] python进程及线程编程
什么是进程: 简单来讲,进程就是操作系统中运行的程序或任务,进程和程序的区别在于进程是动态的,而程序是静态的.进程是操作系统资源管理的最小单位. 什么是线程: 线程是进程的一个实体,是cpu调度和分派 ...
随机推荐
- Linux命令行增强版
0. 前言 周末大早上的,没事做,了解下这几个命令了,哎~~~. 正常情况下,Linux下的命令行,界面比较丑,命令行命令有时候也不是很友好,下面就通过这几个命令或工具,美化一下命令行. 1. oh- ...
- tensorflow 笔记14:tf.expand_dims和tf.squeeze函数
tf.expand_dims和tf.squeeze函数 一.tf.expand_dims() Function tf.expand_dims(input, axis=None, name=None, ...
- [20170713] 无法访问SQL Server
背景: 朋友的环境第二天突然访问不了SQL Server,远程SQL Server用户无法登陆,但是本地SQL Server用户登录正常. 报错: 用户XX登录失败(MicroSoft SQL Ser ...
- 理解、学习与使用 JAVA 中的 OPTIONAL<转>
从 Java 8 引入的一个很有趣的特性是 Optional 类.Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) —— 每个 Java 程序员都 ...
- halcon之NCC匹配
NCC匹配 基于Normalized cross correlation(NCC)用来比较两幅图像的相似程度已经是一个常见的图像处理手段.在工业生产环节检测.监控领域对对象检测与识别均有应用.NCC算 ...
- Nginx+Keepalived+Tomcat高可用负载均衡,Zookeeper集群配置,Mysql(MariaDB)搭建,Redis安装,FTP配置
JDK 安装步骤 下载 http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html rpm ...
- centos系统安装rar解压工具unar
centOS上不支持rar解压,需要额外安装软件,收费版是unrar,免费版是unar unar在centOS上安装需要源码编译,下面是安装方法: 1.安装依赖 yum install gnustep ...
- 认识 SSH 密钥对
SSH 密钥对是阿里云为您提供的新的远程登录 ECS 实例的认证方式. 相较于传统的用户名和密码认证方式,SSH 密钥对有以下特点: 仅适用于 Linux 实例: SSH 密钥对登录认证更为安全可靠: ...
- node-sass 安装失败 Failed at the node-sass@4.9.2 postinstall script的解决
控制台运行npm install时报错,报错信息如下: npm ERR! code ELIFECYCLEnpm ERR! errno 1npm ERR! node-sass@4.9.2 postins ...
- 更改了react-redux 官方网站的todolist结构
最近在学习胡子大哈的react小书,内容讲的由浅入深,很值得react,react-redux小白一读. 废话不多说直接上地址:http://huziketang.mangojuice.top/boo ...