• 并发编程之多线程


    一多线程的概念介绍

    threading模块介绍

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

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

    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())
    方式一
    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('')
    方式二

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

    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     
    线程的开启速度大于进程的开启速度
    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的不同
    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
    同一进程内的线程共享该进程的数据

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

    练习

    多线程实现并发

    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()
    服务端
    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()
    客户端

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

     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()+'
    ') #写进文件
    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()
    代码实现

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

     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())有相同的结果
     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与守护线程

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

    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.守护线程:主线程运行完了守护的那个还没有干掉,主线程等非守护线程全都结束它才结束

     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())
    守护进程和守护线程
    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早期版本的遗留问题
    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方法释放锁:

    1 import threading
    2 mutex = threading.Lock()
    3 mutex.aquire()
    4 '''
    5 对公共数据的操作
    6 '''
    7 mutex.release()
    锁的格式
    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锁和互斥锁综合分析(重点!!!)

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

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

     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的区别(重点!!!)
  • 相关阅读:
    书籍下载点
    总结一下散乱的开发点滴(3) (高手勿入)
    [收藏]Dynamic Controls in ASP.NET
    [收藏] ORACLE中函数
    面试题收集,面试和被面试中煎熬~~
    一句SQL得出表中重复的数据(TMP)
    总结一下散乱的开发点滴(4) (高手勿入)
    一个同步的例子
    关于学习的反思(下)系网开发记(4)
    关于学习的反思(上)系网开发记(2)
  • 原文地址:https://www.cnblogs.com/bypp/p/8631859.html
Copyright © 2020-2023  润新知