并发编程之多线程

一多线程的概念介绍threading模块介绍threading模块和multiprocessing模块在使用层面,有很大的相似性。二、开启多线程的两种方式11.创建线程的开销比创建进程的开销

大家好,又见面了,我是你们的朋友全栈君。

一多线程的概念介绍

threading模块介绍

threading模块和multiprocessing模块在使用层面,有很大的相似性。

二、开启多线程的两种方式

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

1 1.创建线程的开销比创建进程的开销小,因而创建线程的速度快
 2 from multiprocessing import Process
 3 from threading import Thread
 4 import os
 5 import time
 6 def work():
 7     print('<%s> is running'%os.getpid())
 8     time.sleep(2)
 9     print('<%s> is done'%os.getpid())
10 
11 if __name__ == '__main__':
12     t=Thread(target=work,)
13     # t= Process(target=work,)
14     t.start()
15     print('',os.getpid())

方式一

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

1 from threading import Thread
 2 import time
 3 class Work(Thread):
 4     def __init__(self,name):
 5         super().__init__()
 6         self.name = name
 7     def run(self):
 8         # time.sleep(2)
 9         print('%s say hell'%self.name)
10 if __name__ == '__main__':
11     t = Work('egon')
12     t.start()
13     print('')

方式二

在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

1 from  multiprocessing import Process
 2 from threading import Thread
 3 import time
 4 def work():
 5     time.sleep(2)
 6     print('hello')
 7 if __name__ == '__main__':
 8     t = Thread(target=work)#如果等上几秒,他会在开启的过程中先打印主,如果不等会先打印hello
 9     # t = Process(target=work) #子进程会先打印主,
10     t.start()
11     print('')
12     

线程的开启速度大于进程的开启速度

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

1 # 2.----------
 2 from  multiprocessing import Process
 3 from threading import Thread
 4 import os
 5 def work():
 6     print('hello',os.getpid())
 7 if __name__ == '__main__':
 8     #在主进程下开启多个线程,每个线程都跟主进程的pid一样
 9     t1= Thread(target=work)
10     t2 = Thread(target=work)
11     t1.start()
12     t2.start()
13     print('主线程pid',os.getpid())
14 
15     #来多个进程,每个进程都有不同的pid
16     p1 = Process(target=work)
17     p2 = Process(target=work)
18     p1.start()
19     p2.start()
20     print('主进程pid', os.getpid())

在同一个进程下开多个进程和开多个线程的pid的不同

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

1 from  threading import Thread
 2 from multiprocessing import  Process
 3 import os
 4 def work():
 5     global n
 6     n-=1
 7     print(n)  #所以被改成99了
 8 n = 100
 9 if __name__ == '__main__':
10     # p = Process(target=work)
11     p = Thread(target=work)  #当开启的是线程的时候,因为同一进程内的线程之间共享进程内的数据
12                             #所以打印的n为99
13     p.start()
14     p.join()
15     print('',n) #毫无疑问子进程p已经将自己的全局的n改成了0,
16     # 但改的仅仅是它自己的,查看父进程的n仍然为100

同一进程内的线程共享该进程的数据

进程之间是互相隔离的,不共享。需要借助第三方来完成共享(借助队列,管道,共享数据)

练习

多线程实现并发

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

1 from socket import *
 2 from threading import Thread
 3 s = socket(AF_INET,SOCK_STREAM)
 4 s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #端口重用
 5 s.bind(('127.0.0.1',8081))
 6 s.listen(5)
 7 print('start running...')
 8 def talk(coon,addr):
 9     while True:  # 通信循环
10         try:
11             cmd = coon.recv(1024)
12             print(cmd.decode('utf-8'))
13             if not cmd: break
14             coon.send(cmd.upper())
15             print('发送的是%s'%cmd.upper().decode('utf-8'))
16         except Exception:
17             break
18     coon.close()
19 if __name__ == '__main__':
20     while True:#链接循环
21         coon,addr = s.accept()
22         print(coon,addr)
23         p =Thread(target=talk,args=(coon,addr))
24         p.start()
25     s.close()

服务端

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

1 from socket import *
 2 c = socket(AF_INET,SOCK_STREAM)
 3 c.connect(('127.0.0.1',8081))
 4 while True:
 5     cmd = input('>>:').strip()
 6     if not cmd:continue
 7     c.send(cmd.encode('utf-8'))
 8     data = c.recv(1024)
 9     print('接受的是%s'%data.decode('utf-8'))
10 c.close()

客户端

三个任务,一个接收用户输入,一个将用户输入的内容格式化成大写,一个将格式化后的结果存入文件

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

 1 from threading import Thread
 2 import os
 3 input_l = []
 4 format_l = []
 5 def talk(): #监听输入任务
 6     while True:
 7         cmd = input('>>:').strip()
 8         if not cmd:continue
 9         input_l.append(cmd)
10 
11 def format():
12      while True:
13          if input_l:
14              res = input_l.pop()#取出来
15              format_l.append(res.upper()) #取出来后变大写
16 def save():
17     while True:
18         if format_l:  #如果format_l不为空
19             with open('db','a') as f:
20                 f.write(format_l.pop()+'\n') #写进文件
21                 f.flush()
22 if __name__ == '__main__':
23     t1=Thread(target=talk)
24     t2=Thread(target=format)
25     t3=Thread(target=save)
26     t1.start()
27     t2.start()
28     t3.start()

代码实现

四、多线程共享同一个进程内的地址空间 

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

 1 from threading import Thread
 2 from multiprocessing import Process
 3 import os
 4 n = 100
 5 def talk():
 6     global n
 7     n-=100
 8     print(n)
 9 if __name__ == '__main__':
10     t = Thread(target=talk) #如果开启的是线程的话,n=0
11     # t = Process(target=talk) #如果开启的是进程的话,n=100
12     t.start()
13     t.join()
14     print('',n)

View Code

五、线程对象的其他属性和方法

Thread实例对象的方法
  # isAlive(): 返回线程是否活动的。
  # getName(): 返回线程名。
  # setName(): 设置线程名。

threading模块提供的一些方法:
  # threading.currentThread(): 返回当前的线程变量。
  # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果
<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

 1 from  threading import Thread
 2 from multiprocessing import Process
 3 import time,os,threading
 4 def work():
 5     time.sleep(2)
 6     print('%s is running' % threading.currentThread().getName())
 7     print(threading.current_thread()) #其他线程
 8     print(threading.currentThread().getName()) #得到其他线程的名字
 9 if __name__ == '__main__':
10     t = Thread(target=work)
11     t.start()
12 
13     print(threading.current_thread().getName())  #主线程的名字
14     print(threading.current_thread()) #主线程
15     print(threading.enumerate()) #连同主线程在内有两个运行的线程
16     time.sleep(2)
17     print(t.is_alive()) #判断线程是否存活
18     print(threading.activeCount())
19     print('')

线程的其他属性和方法

六、join与守护线程

主进程等所有的非守护的子进程结束他才结束(回收它子进程的资源):(有父子关系)
主线程等非守护线程全都结束它才结束: (没父子关系)

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

1 from  threading import Thread
 2 import time,os
 3 def talk():
 4     time.sleep(3)
 5     print('%s is running..'%os.getpid())
 6 if __name__ == '__main__':
 7     t = Thread(target=talk)
 8     t.start()
 9     t.join()  #主进程在等子进程结束
10     print('')

jion

守护线程与守护进程的区别

1.守护进程:主进程会等到所有的非守护进程结束,才销毁守护进程。也就是说(主进程运行完了被守护的那个就干掉了)

2.守护线程:主线程运行完了守护的那个还没有干掉,主线程等非守护线程全都结束它才结束

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

 1 from  multiprocessing import Process
 2 from threading import Thread,currentThread
 3 import time,os
 4 def talk1():
 5     time.sleep(2)
 6     print('hello')
 7 def talk2():
 8     time.sleep(2)
 9     print('you see see')
10 if __name__ == '__main__':
11     t1 = Thread(target=talk1)
12     t2 = Thread(target=talk2)
13     # t1 = Process(target=talk1)
14     # t2 = Process(target=talk2)
15     t1.daemon = True
16     t1.start()
17     t2.start()
18     print('主线程',os.getpid())

守护进程和守护线程

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

1 #3 --------迷惑人的例子
 2 from threading import Thread
 3 import time
 4 def foo():
 5     print(123)
 6     # time.sleep(10) #如果这个等的时间大于下面等的时间,就把不打印end123了
 7     time.sleep(2)  #如果这个等的时间小于下面等的时间,就把end123也打印了
 8     print('end123')
 9 def bar():
10     print(456)
11     # time.sleep(5)
12     time.sleep(10)
13     print('end456')
14 if __name__ == '__main__':
15     t1 = Thread(target=foo)
16     t2 = Thread(target=bar)
17     t1.daemon = True #主线程运行完了守护的那个还没有干掉,
18     # 主线程等非守护线程全都结束它才结束
19     t1.start()
20     t2.start()
21     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早期版本的遗留问题
<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

1 from threading import Thread,Lock
 2 import time
 3 n=100
 4 def work():
 5     mutex.acquire()
 6     global n
 7     temp=n
 8     time.sleep(0.01)
 9     n=temp-1
10     mutex.release()
11 if __name__ == '__main__':
12     mutex=Lock()
13     t_l=[]
14     s=time.time()
15     for i in range(100):
16         t=Thread(target=work)
17         t_l.append(t)
18         t.start()
19     for t in t_l:
20         t.join()
21     print('%s:%s' %(time.time()-s,n))

全局解释锁

锁通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁:

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

1 import threading
2 mutex = threading.Lock()
3 mutex.aquire()
4 '''
5 对公共数据的操作
6 '''
7 mutex.release()

锁的格式

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

1 分析:
2   1.100个线程去抢GIL锁,即抢执行权限
3      2. 肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
4      3. 极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
5     4.直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程

GLL锁和互斥锁综合分析(重点!!!)

 如果不加锁:并发执行,速度快,数据不安全。

加锁:串行执行,速度慢,数据安全。

<span role="heading" aria-level="2">并发编程之多线程
<span role="heading" aria-level="2">并发编程之多线程

 1 #不加锁:并发执行,速度快,数据不安全
  2 from threading import current_thread,Thread,Lock
  3 import os,time
  4 def task():
  5     global n
  6     print('%s is running' %current_thread().getName())
  7     temp=n
  8     time.sleep(0.5)
  9     n=temp-1
 10 
 11 
 12 if __name__ == '__main__':
 13     n=100
 14     lock=Lock()
 15     threads=[]
 16     start_time=time.time()
 17     for i in range(100):
 18         t=Thread(target=task)
 19         threads.append(t)
 20         t.start()
 21     for t in threads:
 22         t.join()
 23 
 24     stop_time=time.time()
 25     print('主:%s n:%s' %(stop_time-start_time,n))
 26 
 27 '''
 28 Thread-1 is running
 29 Thread-2 is running
 30 ......
 31 Thread-100 is running
 32 主:0.5216062068939209 n:99
 33 '''
 34 
 35 
 36 #不加锁:未加锁部分并发执行,加锁部分串行执行,速度慢,数据安全
 37 from threading import current_thread,Thread,Lock
 38 import os,time
 39 def task():
 40     #未加锁的代码并发运行
 41     time.sleep(3)
 42     print('%s start to run' %current_thread().getName())
 43     global n
 44     #加锁的代码串行运行
 45     lock.acquire()
 46     temp=n
 47     time.sleep(0.5)
 48     n=temp-1
 49     lock.release()
 50 
 51 if __name__ == '__main__':
 52     n=100
 53     lock=Lock()
 54     threads=[]
 55     start_time=time.time()
 56     for i in range(100):
 57         t=Thread(target=task)
 58         threads.append(t)
 59         t.start()
 60     for t in threads:
 61         t.join()
 62     stop_time=time.time()
 63     print('主:%s n:%s' %(stop_time-start_time,n))
 64 
 65 '''
 66 Thread-1 is running
 67 Thread-2 is running
 68 ......
 69 Thread-100 is running
 70 主:53.294203758239746 n:0
 71 '''
 72 
 73 #有的同学可能有疑问:既然加锁会让运行变成串行,那么我在start之后立即使用join,就不用加锁了啊,也是串行的效果啊
 74 #没错:在start之后立刻使用jion,肯定会将100个任务的执行变成串行,毫无疑问,最终n的结果也肯定是0,是安全的,但问题是
 75 #start后立即join:任务内的所有代码都是串行执行的,而加锁,只是加锁的部分即修改共享数据的部分是串行的
 76 #单从保证数据安全方面,二者都可以实现,但很明显是加锁的效率更高.
 77 from threading import current_thread,Thread,Lock
 78 import os,time
 79 def task():
 80     time.sleep(3)
 81     print('%s start to run' %current_thread().getName())
 82     global n
 83     temp=n
 84     time.sleep(0.5)
 85     n=temp-1
 86 
 87 
 88 if __name__ == '__main__':
 89     n=100
 90     lock=Lock()
 91     start_time=time.time()
 92     for i in range(100):
 93         t=Thread(target=task)
 94         t.start()
 95         t.join()
 96     stop_time=time.time()
 97     print('主:%s n:%s' %(stop_time-start_time,n))
 98 
 99 '''
100 Thread-1 start to run
101 Thread-2 start to run
102 ......
103 Thread-100 start to run
104 主:350.6937336921692 n:0 #耗时是多么的恐怖
105 '''

互斥锁与jion的区别(重点!!!)

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/124482.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • java逻辑删除代码_MybatisPlus实现逻辑删除功能

    java逻辑删除代码_MybatisPlus实现逻辑删除功能逻辑删除你有没有见过某些网站进行一些删除操作之后,你看不到记录了但是管理员却能够查看到。这里就运用到了逻辑删除。什么是逻辑删除?逻辑删除的本质是修改操作,所谓的逻辑删除其实并不是真正的删除,而是在表中将对应的是否删除标识(deleted)或者说是状态字段(status)做修改操作。比如0是未删除,1是删除。在逻辑上数据是被删除的,但数据本身依然存在库中。对应的SQL语句:updateusers…

  • 八电极脂肪秤方案软硬件与APP功能介绍[通俗易懂]

    八电极脂肪秤软硬件端功能说明:  LED显示:    1、3个超白光LED    2、显示数字:188.8.8(两个小数点)    3、显示符号:ble、kg指示符    4、不显示体脂率(由APP端显示)    电源:4.5V的3节干电池    开机:压力开机,上秤重量达到开机压力值(5±2kg)开机    按键:单位键,无实体按键,    称重范围:标称:5-180kg,    分度:0.05kg    小重量刷零:最小锁定重量5kg,5kg以下重

  • pipeline和baseline是什么?

    pipeline和baseline是什么?昨天和刚来项目的机器学习小白解释了一边什么baseline和pipeline,今天在这里总结一下什么是baseline和pipeline。1.pipeline1.1从管道符到pipeline

  • 设置Textview最大长度,超出显示省略号

    其中很关键推荐:http://www.cnblogs.com/roucheng/p/androidwujie.html

    2021年12月27日
  • 几款软件加密/加壳工具的比较「建议收藏」

    几款软件加密/加壳工具的比较「建议收藏」几款.Net加密/加壳工具的比较前言使用过.NET的程序员都知道,.NET是一个巨大的跨时代进步,它开发效率高、功能强、界面观、耐用、新的语言C#已经提交为行业规范、CLR共公运行库资源丰富,这所有的特点标志着它成为主流编程语言是必然的。可是它也有一个缺点,那就是编译好的程序集可以完全反编译成源代码,这给一些不法份子提供了很好的机会,试想想,您辛苦的劳动成果就这样给了别…

  • eric6教程(电脑的配置基本知识)

    欢迎加入QQ群:853840665,一块学习讨论单击菜单栏Setting-&gt;Preferences,进入设置界面 下面从上而下设置比较常用的设置,其他的各位可以自行研究1.设置编译器2.设置API3设置自动补全4.设置自动检查时间间隔默认是5S,我在用的时候发现经常代码该正确了,却还是提示错误,这里把时间改短5.设置字体大小…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号