python并发编程之多线程1
一多线程的概念介绍
threading模块介绍
threading模块和multiprocessing模块在使用层面,有很大的相似性。
二、开启多线程的两种方式
- 1.创建线程的开销比创建进程的开销小,因而创建线程的速度快
- from multiprocessing import Process
- from threading import Thread
- import os
- import time
- def work():
- print('<%s> is running'%os.getpid())
- time.sleep(2)
- print('<%s> is done'%os.getpid())
- if __name__ == '__main__':
- t=Thread(target=work,)
- # t= Process(target=work,)
- t.start()
- print('主',os.getpid())
开启进程的第一种方式
- from threading import Thread
- import time
- class Work(Thread):
- def __init__(self,name):
- super().__init__()
- self.name = name
- def run(self):
- # time.sleep(2)
- print('%s say hell'%self.name)
- if __name__ == '__main__':
- t = Work('egon')
- t.start()
- print('主')
开启线程的第二种方式(用类)
在一个进程下开启多个线程与在一个进程下开启多个子进程的区别
- from multiprocessing import Process
- from threading import Thread
- import time
- def work():
- time.sleep(2)
- print('hello')
- if __name__ == '__main__':
- t = Thread(target=work)#如果等上几秒,他会在开启的过程中先打印主,如果不等会先打印hello
- # t = Process(target=work) #子进程会先打印主,
- t.start()
- print('主')
线程的开启速度大于进程的开启速度
- # 2.----------
- from multiprocessing import Process
- from threading import Thread
- import os
- def work():
- print('hello',os.getpid())
- if __name__ == '__main__':
- #在主进程下开启多个线程,每个线程都跟主进程的pid一样
- t1= Thread(target=work)
- t2 = Thread(target=work)
- t1.start()
- t2.start()
- print('主线程pid',os.getpid())
- #来多个进程,每个进程都有不同的pid
- p1 = Process(target=work)
- p2 = Process(target=work)
- p1.start()
- p2.start()
- print('主进程pid', os.getpid())
在同一个进程下开多个进程和开多个线程的pid的不同
- from threading import Thread
- from multiprocessing import Process
- import os
- def work():
- global n
- n-=1
- print(n) #所以被改成99了
- n = 100
- if __name__ == '__main__':
- # p = Process(target=work)
- p = Thread(target=work) #当开启的是线程的时候,因为同一进程内的线程之间共享进程内的数据
- #所以打印的n为99
- p.start()
- p.join()
- print('主',n) #毫无疑问子进程p已经将自己的全局的n改成了0,
- # 但改的仅仅是它自己的,查看父进程的n仍然为100
同一进程内的线程共享该进程的数据
进程之间是互相隔离的,不共享。需要借助第三方来完成共享(借助队列,管道,共享数据)
三、练习
练习一:多线程实现并发
- from socket import *
- from threading import Thread
- s = socket(AF_INET,SOCK_STREAM)
- s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #端口重用
- s.bind(('127.0.0.1',8081))
- s.listen(5)
- print('start running...')
- def talk(coon,addr):
- while True: # 通信循环
- try:
- cmd = coon.recv(1024)
- print(cmd.decode('utf-8'))
- if not cmd: break
- coon.send(cmd.upper())
- print('发送的是%s'%cmd.upper().decode('utf-8'))
- except Exception:
- break
- coon.close()
- if __name__ == '__main__':
- while True:#链接循环
- coon,addr = s.accept()
- print(coon,addr)
- p =Thread(target=talk,args=(coon,addr))
- p.start()
- s.close()
服务端
- from socket import *
- c = socket(AF_INET,SOCK_STREAM)
- c.connect(('127.0.0.1',8081))
- while True:
- cmd = input('>>:').strip()
- if not cmd:continue
- c.send(cmd.encode('utf-8'))
- data = c.recv(1024)
- print('接受的是%s'%data.decode('utf-8'))
- c.close()
客户端
练习二:三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件
- from threading import Thread
- import os
- input_l = []
- format_l = []
- def talk(): #监听输入任务
- while True:
- cmd = input('>>:').strip()
- if not cmd:continue
- input_l.append(cmd)
- def format():
- while True:
- if input_l:
- res = input_l.pop()#取出来
- format_l.append(res.upper()) #取出来后变大写
- def save():
- while True:
- if format_l: #如果format_l不为空
- with open('db','a') as f:
- f.write(format_l.pop()+'\n') #写进文件
- f.flush()
- if __name__ == '__main__':
- t1=Thread(target=talk)
- t2=Thread(target=format)
- t3=Thread(target=save)
- t1.start()
- t2.start()
- t3.start()
答案
四、多线程共享同一个进程内的地址空间
- from threading import Thread
- from multiprocessing import Process
- import os
- n = 100
- def talk():
- global n
- n-=100
- print(n)
- if __name__ == '__main__':
- t = Thread(target=talk) #如果开启的是线程的话,n=0
- # t = Process(target=talk) #如果开启的是进程的话,n=100
- t.start()
- t.join()
- print('主',n)
五、线程对象的其他属性和方法
- Thread实例对象的方法
- # isAlive(): 返回线程是否活动的。
- # getName(): 返回线程名。
- # setName(): 设置线程名。
- threading模块提供的一些方法:
- # threading.currentThread(): 返回当前的线程变量。
- # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
- # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
- from threading import Thread
- from multiprocessing import Process
- import time,os,threading
- def work():
- time.sleep(2)
- print('%s is running' % threading.currentThread().getName())
- print(threading.current_thread()) #其他线程
- print(threading.currentThread().getName()) #得到其他线程的名字
- if __name__ == '__main__':
- t = Thread(target=work)
- t.start()
- print(threading.current_thread().getName()) #主线程的名字
- print(threading.current_thread()) #主线程
- print(threading.enumerate()) #连同主线程在内有两个运行的线程
- time.sleep(2)
- print(t.is_alive()) #判断线程是否存活
- print(threading.activeCount())
- print('主')
线程的其他属性和方法
六、join与守护线程
主进程等所有的非守护的子进程结束他才结束(回收它子进程的资源):(有父子关系)
主线程等非守护线程全都结束它才结束: (没父子关系)
- from threading import Thread
- import time,os
- def talk():
- time.sleep(3)
- print('%s is running..'%os.getpid())
- if __name__ == '__main__':
- t = Thread(target=talk)
- t.start()
- t.join() #主进程在等子进程结束
- print('主')
join
守护线程与守护进程的区别
1.守护进程:主进程会等到所有的非守护进程结束,才销毁守护进程。也就是说(主进程运行完了被守护的那个就干掉了)
2.守护线程:主线程运行完了守护的那个还没有干掉,主线程等非守护线程全都结束它才结束
- from multiprocessing import Process
- from threading import Thread,currentThread
- import time,os
- def talk1():
- time.sleep(2)
- print('hello')
- def talk2():
- time.sleep(2)
- print('you see see')
- if __name__ == '__main__':
- t1 = Thread(target=talk1)
- t2 = Thread(target=talk2)
- # t1 = Process(target=talk1)
- # t2 = Process(target=talk2)
- t1.daemon = True
- t1.start()
- t2.start()
- print('主线程',os.getpid())
守护进程和守护线程
- #3 --------迷惑人的例子
- from threading import Thread
- import time
- def foo():
- print(123)
- # time.sleep(10) #如果这个等的时间大于下面等的时间,就把不打印end123了
- time.sleep(2) #如果这个等的时间小于下面等的时间,就把end123也打印了
- print('end123')
- def bar():
- print(456)
- # time.sleep(5)
- time.sleep(10)
- print('end456')
- if __name__ == '__main__':
- t1 = Thread(target=foo)
- t2 = Thread(target=bar)
- t1.daemon = True #主线程运行完了守护的那个还没有干掉,
- # 主线程等非守护线程全都结束它才结束
- t1.start()
- t2.start()
- print('main---------')
一个诱惑人的例子
七、GIL与Lock
1.python GIL(Global Interpreter Lock) #全局的解释器锁
2.锁的目的:牺牲了效率,保证了数据的安全
3.保护不同的数据加不同的锁()
4.python自带垃圾回收
5.谁拿到GIL锁就让谁得到Cpython解释器的执行权限
6.GIT锁保护的是Cpython解释器数据的安全,而不会保护你自己程序的数据的安全
7.GIL锁当遇到阻塞的时候,就被迫的吧锁给释放了,那么其他的就开始抢锁了,抢到
后吧值修改了,但是第一个拿到的还在原本拿到的那个数据的那停留着呢,当再次拿
到锁的时候,数据已经修改了,而你还拿的原来的,这样就混乱了,所以也就保证不了
数据的安全了。
8.那么怎么解决数据的安全ne ?
自己再给加吧锁:mutex=Lock()
同步锁
GIL 与Lock是两把锁,保护的数据不一样,前者是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),后者是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock
过程分析:所有线程抢的是GIL锁,或者说所有线程抢的是执行权限
线程1抢到GIL锁,拿到执行权限,开始执行,然后加了一把Lock,还没有执行完毕,即线程1还未释放Lock,有可能线程2抢到GIL锁,开始执行,执行过程中发现Lock还没有被线程1释放,于是线程2进入阻塞,被夺走执行权限,有可能线程1拿到GIL,然后正常执行到释放Lock。。。这就导致了串行运行的效果
既然是串行,那我们执行
t1.start()
t1.join
t2.start()
t2.join()
这也是串行执行啊,为何还要加Lock呢,需知join是等待t1所有的代码执行完,相当于锁住了t1的所有代码,而Lock只是锁住一部分操作共享数据的代码。
因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是Python早期版本的遗留问题。
- from threading import Thread,Lock
- import time
- n=100
- def work():
- mutex.acquire()
- global n
- temp=n
- time.sleep(0.01)
- n=temp-1
- mutex.release()
- if __name__ == '__main__':
- mutex=Lock()
- t_l=[]
- s=time.time()
- for i in range(100):
- t=Thread(target=work)
- t_l.append(t)
- t.start()
- for t in t_l:
- t.join()
- print('%s:%s' %(time.time()-s,n))
全局解释锁
锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:
- import threading
- mutex = threading.Lock()
- mutex.aquire()
- '''
- 对公共数据的操作
- '''
- mutex.release()
锁的格式
- 分析:
- 1.100个线程去抢GIL锁,即抢执行权限
- 2. 肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
- 3. 极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
- 4.直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程
GIL锁和互斥锁综合分析(重点)
如果不加锁:并发执行,速度快,数据不安全。
加锁:串行执行,速度慢,数据安全。
- #不加锁:并发执行,速度快,数据不安全
- from threading import current_thread,Thread,Lock
- import os,time
- def task():
- global n
- print('%s is running' %current_thread().getName())
- temp=n
- time.sleep(0.5)
- n=temp-1
- if __name__ == '__main__':
- n=100
- lock=Lock()
- threads=[]
- start_time=time.time()
- for i in range(100):
- t=Thread(target=task)
- threads.append(t)
- t.start()
- for t in threads:
- t.join()
- stop_time=time.time()
- print('主:%s n:%s' %(stop_time-start_time,n))
- '''
- Thread-1 is running
- Thread-2 is running
- ......
- Thread-100 is running
- 主:0.5216062068939209 n:99
- '''
- #不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
- from threading import current_thread,Thread,Lock
- import os,time
- def task():
- #未加锁的代码并发运行
- time.sleep(3)
- print('%s start to run' %current_thread().getName())
- global n
- #加锁的代码串行运行
- lock.acquire()
- temp=n
- time.sleep(0.5)
- n=temp-1
- lock.release()
- if __name__ == '__main__':
- n=100
- lock=Lock()
- threads=[]
- start_time=time.time()
- for i in range(100):
- t=Thread(target=task)
- threads.append(t)
- t.start()
- for t in threads:
- t.join()
- stop_time=time.time()
- print('主:%s n:%s' %(stop_time-start_time,n))
- '''
- Thread-1 is running
- Thread-2 is running
- ......
- Thread-100 is running
- 主:53.294203758239746 n:0
- '''
- #有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
- #没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是
- #start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
- #单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
- from threading import current_thread,Thread,Lock
- import os,time
- def task():
- time.sleep(3)
- print('%s start to run' %current_thread().getName())
- global n
- temp=n
- time.sleep(0.5)
- n=temp-1
- if __name__ == '__main__':
- n=100
- lock=Lock()
- start_time=time.time()
- for i in range(100):
- t=Thread(target=task)
- t.start()
- t.join()
- stop_time=time.time()
- print('主:%s n:%s' %(stop_time-start_time,n))
- '''
- Thread-1 start to run
- Thread-2 start to run
- ......
- Thread-100 start to run
- 主:350.6937336921692 n:0 #耗时是多么的恐怖
- '''
互斥锁与join的区别(重点!!!)
python并发编程之多线程1的更多相关文章
- Python并发编程04 /多线程、生产消费者模型、线程进程对比、线程的方法、线程join、守护线程、线程互斥锁
Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线程join.守护线程.线程互斥锁 目录 Python并发编程04 /多线程.生产消费者模型.线程进程对比.线程的方法.线 ...
- 29 python 并发编程之多线程理论
一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程 车间负责把资源整合 ...
- 三 python并发编程之多线程-理论
一 什么是线程 在传统操作系统中,每个进程有一个地址空间,而且默认就有一个控制线程 线程顾名思义,就是一条流水线工作的过程,一条流水线必须属于一个车间,一个车间的工作过程是一个进程 车间负责把资源整合 ...
- python并发编程之多线程理论部分
阅读目录 一 什么是线程 二 线程的创建开销小 三 线程与进程的区别 四 为何要用多线程 五 多线程的应用举例 六 经典的线程模型(了解) 七 POSIX线程(了解) 八 在用户空间实现的线程(了解) ...
- 35、python并发编程之多线程(理论篇)
一 什么是线程 二 线程的创建开销小 三 线程与进程的区别 四 为何要用多线程 五 多线程的应用举例 六 经典的线程模型(了解) 七 POSIX线程(了解) 八 在用户空间实现的线程(了解) 九 在内 ...
- python并发编程之多线程
一 同步锁 注意: 1线程抢的是GIL锁,GIL锁就是执行权限,拿到权限后才能拿到互斥锁Lock,但是如果发现Lock没有被释放而阻塞,则立即交出拿到的执行权. 2join是等待所有,即整体串行,而 ...
- python并发编程之多线程基础知识点
1.线程理论知识 概念:指的是一条流水线的工作过程的总称,是一个抽象的概念,是CPU基本执行单位. 进程和线程之间的区别: 1. 进程仅仅是一个资源单位,其中包含程序运行所需的资源,而线程就相当于车间 ...
- Python并发编程之多线程使用
目录 一 开启线程的两种方式 二 在一个进程下开启多个线程与在一个进程下开启多个子进程的区别 三 练习 四 线程相关的其他方法 五 守护线程 六 Python GIL(Global Interpret ...
- python并发编程之多线程2------------死锁与递归锁,信号量等
一.死锁现象与递归锁 进程也是有死锁的 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用, 它们都将无法推进下去.此时称系统处于死锁状态或系统 ...
随机推荐
- python加密(MD5)
# import hashlib # # 1. 创建一个MD5对象 # obj = hashlib.md5(b"flkjsdalkfjklasdjfklasjkflasdjklfasdjfl ...
- js数据校验插件
//数据校验 /** *{type:"类型",notEmpty:true,regxp: reg,MaxLength: number,MinLength number,message ...
- LOJ #2058「TJOI / HEOI2016」求和
不错的推柿子题 LOJ #2058 题意:求$\sum\limits_{i=0}^n\sum\limits_{j=0}^nS(i,j)·2^j·j!$其中$ S(n,m)$是第二类斯特林数 $ Sol ...
- LOJ #2587「APIO2018」铁人两项
是不是$ vector$存图非常慢啊...... 题意:求数对$(x,y,z)$的数量使得存在一条$x$到$z$的路径上经过$y$,要求$x,y,z$两两不同 LOJ #2587 $ Solutio ...
- android矩阵详解
android矩阵:http://www.360doc.com/content/11/1215/11/7635_172396706.shtml
- Java8新特性(待更新...)
一.Lambda表达式 二.接口的默认方法与静态方法 三.方法引用 四.重复注解 五.扩展注解的支持 六.Optional 七.Stream 八.Date/Time API (JSR 310) 九.J ...
- 在Linux环境下安装Python3
参考链接:https://blog.csdn.net/zhangdongren/article/details/82685932
- python正则下载图片
import urllib.request import re # 打开图片路径 def open_url(url): #设置请求路径 req = urllib.request.Request(url ...
- 最长增长子序列(LIS)
给定一个无序的整数数组,找到其中最长上升子序列的长度. 示例: 输入: [10,9,2,5,3,7,101,18] 输出: 4 解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4. 说 ...
- cocos开发学习记录
场景的创建和切换 https://blog.csdn.net/lin453701006/article/details/56334578