pathon之多线程详解
一、线程理论
- 1、什么是线程
线程指的是一条流水线的工作过程- 进程根本就不是一个执行单位,进程其实是一个资源单位--------将资源集合到一起:
一个进程内自带一个线程,线程才是CPU上的执行单位- 2、进程VS线程
1、同一进程内的线程们共享该进程内资源,不同进程内的线程资源肯定是隔离的
2、创建线程的开销比创建进程要小的多(大概是100倍)- 把操作系统比做一座工厂进行比对:
工厂=====》车间=====》流水线
操作系统====》进程=====》线程- 开启一个进程,就是申请了一个内存空间,将产生的数据丢掉里面,而代码的运行就是线程
多个进程内存空间彼此是隔离的,而同一个进程下的多个线程,共享该进程内的数据,分散于不同进程之间的线程数据是隔离的- 3、创建进程的开销远大于开启线程的开销:
造进程要向操作系统发请求,由操作系统帮你申请一个内存空间,
在一个进程再开启一个请求,不需要在申请内存空间,只需要告诉操作系统只需要执行代码即可- 创建进程的开销要远大于线程
形象比喻:
如果我们的软件是一个工厂,该工厂有多条流水线,流水线工作需要电源,电源只有一个即cpu(单核cpu)
一个车间就是一个进程,一个车间至少一条流水线(一个进程至少一个线程)
创建一个进程,就是创建一个车间(申请空间,在该空间内建至少一条流水线)
而建线程,就只是在一个车间内造一条流水线,无需申请空间,所以创建开销小- 进程之间是竞争关系,线程之间是协作关系
形象比喻:
车间直接是竞争/抢电源的关系,竞争(不同的进程直接是竞争关系,是不同的程序员写的程序运行的,迅雷抢占其他进程的网速,360把其他进程当做病毒干死)
一个车间的不同流水线式协同工作的关系(同一个进程的线程之间是合作关系,是同一个程序写的程序内开启动,迅雷内的线程是合作关系,不会自己干自己)
二、开启线程的两种方式
方式一、
直接调用线程类,指定开启的子进程函数
- #开启线程的方式一、
- # 常用方式:
- from multiprocessing import Process
- from threading import Thread
- import time
- def task(name):
- print('%s is running' %name) #最先被打印出来
- time.sleep(3)
- print('%s is done' % name) #最后被打印出来
- if __name__ == '__main__': #线程可以不用__main__,但是开启进程必须用,因为开启进程为从新在导入模块
- # t=Thread(target=task,args=('egon',))
- t=Process(target=task,args=('egon',))
- t.start() #开启线程的速速很快,几乎向操作系统发起请求就被开启(不需要申请内存空间);而开启进程要向操作系统发起请求,申请内存空间,而这个时间已经够运行到下一行代码的了
- print('主线程')
- #开启线程打印结果:
- '''
- egon is running
- 主线程
- egon is done
- '''
- #开启进程打印结果:
- '''
- 主线程
- egon is running
- egon is done
- '''
方式二、
自定义一个线程类,并继承Thread类,然后调用自定义类
- #自定义一个类、但是还要用被人内置的方法名
- from multiprocessing import Process
- from threading import Thread
- import time
- class MyThread(Thread):
- def run(self):
- print('%s is running' %self.name)
- time.sleep(3)
- if __name__ == '__main__':
- t=MyThread()
- t.start()
- print('主线程')
三、进程与线程对比
- #2、线程创建开销小-----因为其不需要申请内存空间
- # 线程的开启速度更快
- from threading import Thread
- from multiprocessing import Process
- import os
- def work():
- print('hello')
- if __name__ == '__main__':
- #在主进程下开启线程
- t=Thread(target=work)
- t.start()
- print('主线程/主进程')
- '''
- 打印结果:
- hello #子进程内的打印先被打印出来,因为子进程开启不需要申请内存空间,所以开启速度非常快
- 主线程/主进程
- '''
- #在主进程下开启子进程
- t=Process(target=work)
- t.start()
- print('主线程/主进程')
- '''
- 打印结果:
- 主线程/主进程 #由于子进程的开启需要申请内存空间,需要时间,而代码的运行速度是很快的,所以'主线程/主进程' 会先与子进程的代码被打印出来
- hello
- '''
线程的开销小,开启速度更快
- from threading import Thread
- import time,os
- import time
- x=100
- def task():
- global x
- x=0 #子线程内的修改,可以站在主线程的角度去看,确实变量的值也被修改了
- if __name__ == '__main__':
- t=Thread(target=task,)
- t.start()
- # time.sleep(3) #模拟开启线程和线程进行修改数据的时间
- t.join() #主线程等着子线程运行完毕
- print('主线程',x)
同一进程内的多个线程共享该进程内的资源(数据)
- '''疑问线程有没有pid?
- 线程有没有杀死的需求'''
- from threading import Thread
- from multiprocessing import Process
- import time,os
- '''同一个进程内,主线程和子线程的PID是一样的'''
- def task():
- print('%s is running' %os.getpid())
- time.sleep(3)
- if __name__ == '__main__':
- t=Thread(target=task,)
- t.start()
- print('主线程',os.getpid())
- '''开启多个子进程每个子进程都有不同的PID'''
- p1=Process(target=task)
- p2=Process(target=task)
- p1.start()
- p2.start()
- print('主线程、主进程',os.getpid())
观察PID
四、线程对象的其他方法
- 1、Thread实例对象的方法
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。- 2、threading模块提供的一些方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
3、线程对象其他方法的应用
- from threading import Thread,current_thread,active_count,enumerate
- import time,os
- def task():
- print('%s is running' %current_thread().name) #在当前线程查看自己的线程名
- time.sleep(3)
- print('%s is done' % current_thread().getName()) #在当前线程查看自己的线程名current_thread().name等价于current_thread().getName()
- if __name__ == '__main__':
- t1=Thread(target=task,name='第一个线程')
- # t2=Thread(target=task,)
- # t3=Thread(target=task,)
- t1.start()
- # t2.start()
- # t3.start()
- # print(t1.is_alive()) #判断线程是否存活
- print(active_count()) #活跃的线程个数
- print(enumerate()) #把当前活跃的线程对象放到列表中,可以通过点来使用线程的属性
- current_thread().setName('修改后的主线程名')
- print('主线程',current_thread().name) #查看主线程的名字,也可以修改主线程的名字
五、守护线程
- 守护线程会在什么时候死?
守护线程会等到该进程内所有的非守护线程都结束才跟着结束
守护线程其实守护的是整个进程的运行周期(进程内所有的非守护线程都运行完毕)
强调:
守护进程是父进程结束,守护的进程就随之结束
- from threading import Thread,current_thread
- import time
- def task():
- print('%s is running' %current_thread().name)
- time.sleep(3)
- print('%s is done' % current_thread().name) #守护的线程已经结束,所以看不到
- if __name__ == '__main__':
- t1=Thread(target=task,name='第一个线程')
- t1.daemon = True
- t1.start() #由于开的是线程的速度太开了,所以 print('%s is running' %current_thread().name)会被先打印出来
- print('主线程')
- '''
- 打印结果:
- 第一个线程 is running
- 主线程
- '''
- 守护进程要等所有的非守护进程结束才会结束,而不是主线程结束守护进程就结束
- from threading import Thread
- import time
- def foo():
- print(123)
- time.sleep(5)
- print("end123") #不可能看到,原因是所有的非守护线程都已经运行完毕,自己也就随之结束,只有当睡的时间比非守护线程短才能看到
- def bar():
- print(456)
- time.sleep(3)
- print("end456")
- if __name__ == '__main__':
- t1=Thread(target=foo)
- t2=Thread(target=bar)
- t1.daemon=True
- t1.start()
- t2.start()
- print("main-------")
- '''
- 123
- 456
- main-------
- end456
- '''
六、线程互斥锁
同一时间只有一个人能拿到锁
- from threading import Thread,Lock
- import time
- mutex=Lock()
- x=100
- def task():
- global x
- mutex.acquire() #大家都声明了全局变量,加锁让他们等,同一时间只有一个人拿到
- temp=x #保证100个线程都拿到了x,即都拿到了初始值
- time.sleep(0.1) #都睡了0.1秒,这个时间已经都后面99个线程起来,那么他们谁先谁后抢到锁就不一定了
- x=temp-1 #正常已改改为0
- mutex.release()
- if __name__ == '__main__':
- start=time.time()
- t_l=[]
- for i in range(100): #用for循环是有先后顺序,真实可能是同时起来的
- t=Thread(target=task)
- t_l.append(t)
- t.start() #速度太快了,几乎是start同时
- for t in t_l:
- t.join()
- print('主',x)
- print(time.time()-start)
七、死锁现象及解决方法递归锁
死锁现象:
- 死锁现象是由于锁套锁导致的,两个线程互相拿到对方的锁就都被锁死了
- '''死锁现象'''
- from threading import Thread,Lock,RLock
- import time
- mutexA=Lock()
- mutexB=Lock()
- class MyThread(Thread):
- def run(self):
- self.f1()
- self.f2()
- def f1(self):
- mutexA.acquire()
- print('%s 拿到了A锁' %self.name)
- mutexB.acquire()
- print('%s 拿到了B锁' %self.name)
- mutexB.release()
- mutexA.release()
- def f2(self):
- mutexB.acquire()
- print('%s 拿到了B锁' %self.name)
- time.sleep(0.1)
- mutexA.acquire()
- print('%s 拿到了A锁' %self.name)
- mutexA.release()
- mutexB.release()
- if __name__ == '__main__':
- for i in range(10):
- t=MyThread()
- t.start()
- '''
- Thread-1 拿到了A锁 #线程1首先抢到了A锁,还没有释放,其他人只有等线程1把A锁释放,才能抢到A锁
- Thread-1 拿到了B锁 #线程1又抢到了B锁,然后释放掉了B锁和A锁,那么其他人此时就可以争抢A锁,
- Thread-1 拿到了B锁 #线程1紧接着又拿到了B锁,睡了一会准备去抢A锁,但是此时A锁已经被被人抢走,所以就会在原地阻塞住
- Thread-2 拿到了A锁 #线程1释放的A锁被线程2抢到,然后线程2又想去抢A锁,但是此时B锁已经被线程1抢走,所以线程1就被阻塞住
- #这就导致线程1和线程2互相锁死的现象,我们采用递归锁来进行解决死锁现象
死锁现象
解决死锁现象
- from threading import Thread,Lock,RLock
- import time
- mutexA=Lock()
- mutexB=Lock()
- mutexA=mutexB=RLock() #RLock递归锁来解决死锁现象,递归锁的特点是可以连续的acquire
- class MyThread(Thread):
- def run(self):
- self.f1()
- self.f2()
- def f1(self):
- mutexA.acquire()
- print('%s 拿到了A锁' %self.name)
- mutexB.acquire()
- print('%s 拿到了B锁' %self.name)
- mutexB.release()
- mutexA.release()
- def f2(self):
- mutexB.acquire()
- print('%s 拿到了B锁' %self.name)
- time.sleep(0.1)
- mutexA.acquire()
- print('%s 拿到了A锁' %self.name)
- mutexA.release()
- mutexB.release()
- if __name__ == '__main__':
- for i in range(10):
- t=MyThread()
- t.start()
- t1=MyThread()
- t1.start()
- t2=MyThread()
- t2.start()
- t3=MyThread()
- t3.start()
- print('主')
递归锁解决死锁现象
八、信号量
- # from multiprocessing import Semaphore
- # 信号量也是一种锁
- from threading import Thread,Semaphore,current_thread
- import time,random
- # 同一时间并发执行的任务数
- # 而互斥锁同一时间只有一个正在运行
- sm=Semaphore(5) #得到一个信号量的对象
- def go_wc():
- sm.acquire()
- print('%s 上厕所ing' %current_thread().getName())
- time.sleep(random.randint(1,3))
- sm.release()
- if __name__ == '__main__':
- for i in range(23):
- t=Thread(target=go_wc)
- t.start()
pathon之多线程详解的更多相关文章
- iOS开发——多线程OC篇&多线程详解
多线程详解 前面介绍了多线程的各种方式及其使用,这里补一点关于多线程的概念及相关技巧与使用,相信前面不懂的地方看了这里之后你就对多线程基本上没有什么问题了! 1——首先ios开发多线程中必须了解的概念 ...
- iOS开发——GCD多线程详解
GCD多线程详解 1. 什么是GCD Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,简单来说,GCD就是iOS一套解决多线程的机制,使用GCD能够最大限度简化多线程 ...
- Java 多线程详解(四)------生产者和消费者
Java 多线程详解(一)------概念的引入:http://www.cnblogs.com/ysocean/p/6882988.html Java 多线程详解(二)------如何创建进程和线程: ...
- java中多线程详解-synchronized
一.介绍 当多个线程涉及到共享数据的时候,就会设计到线程安全的问题.非线程安全其实会在多个线程对同一个对象中的实例变量进行并发访问时发生,产生的后果就是“脏读”.发生脏读,就是取到的数据已经被其他的线 ...
- python多线程详解
目录 python多线程详解 一.线程介绍 什么是线程 为什么要使用多线程 二.线程实现 threading模块 自定义线程 守护线程 主线程等待子线程结束 多线程共享全局变量 互斥锁 递归锁 信号量 ...
- C#多线程详解(一) Thread.Join()的详解
bicabo C#多线程详解(一) Thread.Join()的详解 什么是进程?当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程 ...
- Java多线程详解(二)
评论区留下邮箱可获得<Java多线程设计模式详解> 转载请指明来源 1)后台线程 后台线程是为其他线程服务的一种线程,像JVM的垃圾回收线程就是一种后台线程.后台线程总是等到非后台线程死亡 ...
- Delphi多线程详解
(整理自网络) Delphi多线程处理 1-1多线程的基本概念 WIN 98/NT/2000/XP 是个多任务操作系统,也就是:一个进程可以划分为多个线程,每个线程轮流占用CPU 运行时间和资源,或者 ...
- Java中的多线程详解
如果对什么是线程.什么是进程仍存有疑惑,请先Google之,因为这两个概念不在本文的范围之内. 用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其 ...
随机推荐
- 上拉加载下拉刷新控件WaterRefreshLoadMoreView
上拉加载下拉刷新控件WaterRefreshLoadMoreView 效果: 源码: // // SRSlimeView // @author SR // Modified by JunHan on ...
- 检查windows系统支持的密码套件
Windows 10客户端及Windows server 2016 服务器可以使用powershell 命令获得系统支持的密码套件列表,禁用启用相应的密码套件. #命令链接:https://techn ...
- 批处理文件(Batch Files )
后缀是bat的文件就是批处理文件,是一种文本文件.简单的说,它的作用就是自动的连续执行多条命令,批处理文件的内容就是一条一条的命令. 新建一个批处理abc.bat,里面内容如下:@echo offec ...
- 清除 Exchange 2013/2016/2019 日志和ETL文件
Exchange Server 的2个日志目录会增长的很快,需要定时清理,不然C盘的空间很快就会吃光,以下这个powershell脚本就是用于清理目录下面的日志的,已在生产环境中测试过,没问题: S ...
- EF CodeFirst下的自动迁移
当我们修改数据模型,添加一个如下字段 再次运行程序,会因为数据库结构与模型不一致而报错 为解决以上错误可以采取以下三种方式 1. 删除数据库,重新运行站点,会重新生成数据库,这样就会丢失数据 2. ...
- 张高兴的 Windows 10 IoT 开发笔记:FM 电台模块 KT0803L
This is a Windows 10 IoT Core project on the Raspberry Pi 2/3, coded by C#. GitHub:https://github.co ...
- nginx alias
A path to the file is constructed by merely adding a URI to the value of the root directive. If a UR ...
- java Date日期类和SimpleDateFormat日期类格式
~Date表示特定的时间,精确到毫秒~构造方法:public Date()//构造Date对象并初始化为当前系统的时间public Date(long date) //1970-1-1 0:0:0到指 ...
- php实现动态随机验证码机制(CAPTCHA)
php实现动态随机验证码机制 验证码(CAPTCHA)是“Completely Automated Public Turing test to tell Computers and Humans Ap ...
- iOS 网络缓存总结
一.缓存策略: 1.缓存策略的配置: 缺省缓存策略的存储策略需要服务器的响应配置: 缺省缓存策略的使用需要请求端的配置: 2.缓存策略的缺陷: 移动端比较通用的缓存策略是先使用缓存同时更新本地数据: ...