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的资源,因为所有的多线程代码都可以用单线程来实现.说这个话其 ...
随机推荐
- UNIX高级环境编程(2)FIle I/O - 原子操作、共享文件描述符和I/O控制函数
引言: 本篇通过对open函数的讨论,引入原子操作,多进程通信(共享文件描述符)和内核相关的数据结构. 还会讨论集中常见的文件IO控制函数,包括: dup和dup2 sync,fsync和fdatas ...
- python2.7与3.5共存windows平台安装
文:铁乐与猫 2018-3-18 周日 01.首先是安装python2.7: 官网下载 https://www.python.org 点击安装包进行安装 可以选择自定义的路径 将默认打x的[add p ...
- vue弹出框的封装
依旧是百度不到自己想要的,就自己动手丰衣足食 弹出框做成单独的组件confirm.vue; <template> <transition name="mask-bg-fad ...
- ZT CSDN 如何以最快的速度计算出一个二进制数中1的个数? [
一道算法面试题:如何以最快的速度计算出一个二进制数中1的个数? [问题点数:10分,结帖人weicai_chen] 收藏 weicai_chen weicai_chen 等级: 结帖率:95.12% ...
- CSS3 Transform变形理解与应用
CSS3 Transform变形理解与应用 Transform:对元素进行变形:Transition:对元素某个属性或多个属性的变化,进行控制(时间等),类似flash的补间动画.但只有两个关键贞.开 ...
- python第十四课--排序及自定义函数之案例一:选择排序
案例一:选择排序使用选择排序的思想实现列表数据的升序排序 lt=[45,12,56,-32,-3,44,75,-22,100] length=len(lt) # print('排序前:'+str(lt ...
- easyui 对form扩展
功能描述 easyui 中 combobox 多选赋值方法如下: $('#cbx').combobox('setValues', ['01','02']) 然而,业务中是以 “01,02” 的形式 ...
- MySQL 更改数据库数据存储目录
MySQL数据库默认的数据库文件位于 /var/lib/mysql 下,有时候由于存储规划等原因,需要更改 MySQL 数据库的数据存储目录. 下文总结整理了实践过程的操作步骤. 1 确认MySQ ...
- 【转】numpy中 meshgrid 和 mgrid 的区别和使用
转自:https://www.cnblogs.com/shenxiaolin/p/8854197.html 一.meshgrid函数 meshgrid函数通常使用在数据的矢量化上. 它适用于生成网格型 ...
- pandas数据结构:Series/DataFrame;python函数:range/arange
1. Series Series 是一个类数组的数据结构,同时带有标签(lable)或者说索引(index). 1.1 下边生成一个最简单的Series对象,因为没有给Series指定索引,所以此时会 ...