zoukankan      html  css  js  c++  java
  • 并发编程/GIL

    进程:

    进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中用它来控制和管理进程,它是系统感知进程存在的唯一标识。

    为了实现多道技术,使CPU使用率更高,系统会经常进行进行间的切换,切换会发生在出现IO操作的时候,以及系统固定时间的切换。

    线程:

    线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元,由线程ID、程序计数器、寄存器集合和堆栈共同组成。线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。

    进程与线程的关系:

    1.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。

    2.资源分配给进程,同一进程的所有线程共享进程的所有资源。

    3.CPU分给线程,即真正在CPU上进行的是线程。
    Pyhon的多线程:由于GIL导致同一时刻同一进程只能有一个线程。

    并行和并发:

    并行处理(Parallel Processing)是计算机系统中能同时执行两个或更多个处理的一种计算方法。并行处理可同时工作于同一程序的不同方面。并行处理的主要目的是节省大型和复杂问题的解决时间。并发处理(concurrency Processing):指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机(CPU)上运行,但任一个时刻点上只有一个程序在处理机(CPU)上运行

    并发的关键是你有处理多个任务的能力,不一定要同时。并行的关键是你有同时处理多个任务的能力。所以说,并行是并发的子集

    threading模块

    线程对象的创建:

    thread直接创建:

     1  1 import time
     2  2 import threading
     3  3 def tingge():
     4  4      print('听歌')
     5  5      time.sleep(3)
     6  6      print('听歌结束')
     7  7 
     8  8 def xieboke():
     9  9      print('写博客')
    10 10      time.sleep(5)
    11 11      print('写博客结束')
    12 12 
    13 13 t1 = threading.Thread(target = tingge) #生成线程实例,target=函数名,args = 函数需要传参的参数,默认可以不传
    14 14 t2 = threading.Thread(target = xieboke)
    15 15 
    16 16 t1.start()    #启动进程
    17 17 t2.start()
    18 18 print("ending!")
    19 
    20 
    21 #继承Thread式自定义创建:
    22 import threading
    23 import time
    24 class MyThread(threading.Thread):
    25      
    26      def __init__(self,num):
    27           threading.Threa.__init__(self)
    28           self.num = num
    29      def run(self):
    30           print("running on number:%s" % self.num )
    31           time.sleep(3)
    32 
    33 t1 = MyThread(56)
    34 t2 = MyThread(78)
    35 
    36 t1.start() 
    37 t2.start()
    38 print('ending')

    以上第一段代码就实现了一个并发的效果。tingge与xieboke函数(子线程)跟主逻辑“print('ending!')”(主线程)同时启动,先几乎同时执行【print('听歌')、print('写博客')、print("ending!")】随后IO操作“sleep”,等3S听歌结束,再等2S写博客结束。如果使用串行的方法,程序全部执行则一共需要差不多8S的时间,降低了CPU的使用效率。

    Threading的方法:

    join:在子线程完成运行之前,这个子线程的父线程将一直被阻塞。

    setDaemon:

     '''
             将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。
    
             当我们在程序运行中,执行一个主线程,如果主线程又创建一个子线程,主线程和子线程 就分兵两路,分别运行,那么当主线程完成
    
             想退出时,会检验子线程是否完成。如果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是只要主线程
    
             完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦'''

    这么看来,是不是当要执行多个线程,是不是都可以用Threading来解决呢?
    其实并不是,对于计算密集型任务,Python的多线程并没有用。由于GIL的存在在,一个线程拥有了解释器的访问权之后,其他的所有线程都必须等待它释放解释器的访问权,即使这些线程的下一条指令并不会互相影响。
    因此Python并不能达到一个并发的效果,同一时间只能运行一个线程,所以对于计算密集型任务,多个线程同时启动,CPU就需要频繁的来回切换,这样就加入了大量的切换时间。而串行的方式则只需要切换少数的几次,
    因此对于计算密集型任务,Python的多线程并没有用,而对于上面的IO密集型任务,Python的多线程是有意义的。

    那么Python有没有使用多核的方法呢? 答案是可以的,但只能是开多个进程来实现,那样弊端也显而易见,会增大资源开销而且进程间相互的切换也会非常复杂。
    解决的着重点:协程+多进程/ IO多路复用

    同步锁:LOCK

    多线程和多进程最大的不同在于,多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,而多线程中,所有变量都由所有线程共享,所以,任何一个变量都可以被任何一个线程修改,因此,线程之间共享数据最大的危险在于多个线程同时改一个变量,把内容给改乱了。

    
    
    来看看多个线程同时操作一个变量怎么把内容给改乱了:
    import time
    import threading
    
    def subNum():
         global num
         lock.acquire() #加锁
         temp = num
         time.sleep(0.001)    #定义一个num-1的函数 每次运算前停留0.001S
         num = temp -1
    
    num = 100  #定义一个初始数值num =100
    thread_list = [ ] 
    
    for i in range(100):
         t = threading.Thread(target = subNum)    #开启100个子线程来运行减法函数
         t.start()
         thread_list.append(t)
    
    for t in thread_list:    
         t.join()                  #阻塞主线程运行,所有减法函数执行完得出最后结果
    ptiny('Result:',num)
                
    可以看到,100个子进程并发的进行减法函数计算,正常情况的话,最后的结果应该是0。然而实际运行时候会得出90-95之间的不同结果。究其原因,是因为第一个线程在拿到Num初始值时候,没有立即进行运算,我们用sleep模拟了一次IO操作,在进行IO操作的时候,CPU不会闲着,操作系统会自动切换到其它线程运行,然而此时1线程还没有做出运算并保存,因此线程2拿到的变量num还是100,因此两个做出了相同的操作,线程3..4。。。同样如此,直到线程1完成计算并保存,后面的线程n拿到的数是99依次运算。
    怎么解决这个问题呢?这就需要用到threading提供的同步锁Lock
    import time
    import threading
    
    def subNum():
         global num
         print("验证执行")
         lock.acquire() #加锁
         temp = num
         time.sleep(0.1)
         num = temp -1
         lock.release() #解锁
    
    num = 100
    thread_list = [ ] 
    
    lock = threading.Lock() #定义一个同步锁
    
    for i in range(100):
         t = threading.Thread(target = subNum)
         t.start()
         thread_list.append(t)
    
    for t in thread_list:
         t.join()
    ptiny('Result:',num)
    在线程1进行未完成计算的时候,限制操作系统切换到其它的线程执行,保证了数据的安全可靠性。而函数中其它未上锁的代码可以正常切换,使用灵活。
     
    死锁与递归锁:
     1 import threading
     2 import time
     3 
     4 mutexA = threading.Lock()
     5 mutexB = threading.Lock()
     6 
     7 class MyThread(threading.Thread):
     8     def __init__(self):
     9         threading.Thread.__init__(self)
    10 
    11     def run(self):
    12         self.fun1()
    13         self.fun2()
    14 
    15     def fun1(self):
    16         mutexA.acquire()  # 如果锁被占用,则阻塞在这里,等待锁的释放
    17 
    18         print("I am %s, get res: %s---%s" % (self.name, "ReaA", time.time()))
    19 
    20         mutexB.acquire()
    21         print("I am %s, get res: %s---%s" % (self.name, "RreB", time.time()))
    22         mutexB.release()
    23 
    24         mutexA.release()
    25 
    26     def fun2(self):
    27         mutexB.acquire()
    28         print("I am %s, get res: %s---%s" % (self.name, "ResB", time.time()))
    29         time.sleep(0.2)
    30 
    31         mutexA.acquire()
    32         print("I am %s, get res: %s---%s" % (self.name, "ResA", time.time()))
    33         mutexA.release()
    34 
    35         mutexB.release()
    36 
    37 if __name__ == "__main__":
    38     print("start------------%s" % time.time())
    39 
    40     for i in range(0, 10):
    41         my_thread = MyThread()
    42         my_thread.start()
    View Code

    当代码中有2个锁,锁中套着锁,多个线程同时执行时,可以看到,正常情况下。每个线程会依次A、B、B、A的顺序进行打印,一共10个线程的循环。实际运行的情况是,当线程1走完fun1(),走到线程2时,打印完B,即获得B锁,sleep0.2S,此时,线程2已经竞争到了A锁,而B锁已经被线程1给拿到,因此在此处阻塞,而线程1sleep完成后,跟着执行代码时,A锁已经被线程2占用,所以它也不能执行,2个线程相互胶着互不放手,这样就触发了死锁。如果要解决这个问题就需要用到另一种锁即递归锁。

    在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

     1 import threading
     2 import time
     3 
     4 mutex = threading.RLock()
     5 
     6 class MyThread(threading.Thread):
     7 
     8     def __init__(self):
     9         threading.Thread.__init__(self)
    10 
    11     def run(self):
    12         self.fun1()
    13         self.fun2()
    14 
    15     def fun1(self):
    16 
    17         mutex.acquire()  # 如果锁被占用,则阻塞在这里,等待锁的释放
    18 
    19         print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
    20 
    21         mutex.acquire()
    22         print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
    23         mutex.release()
    24 
    25         mutex.release()
    26 
    27 
    28     def fun2(self):
    29 
    30         mutex.acquire()
    31         print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
    32         time.sleep(0.2)
    33 
    34         mutex.acquire()
    35         print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
    36         mutex.release()
    37 
    38         mutex.release()
    39 
    40 if __name__ == "__main__":
    41 
    42     print("start---------------------------%s"%time.time())
    43 
    44     for i in range(0, 10):
    45         my_thread = MyThread()
    46         my_thread.start()
    View Code

     参考代码

  • 相关阅读:
    ListView之setEmptyView的问题
    AdapterView的相关知识。
    分享一个程序猿的真实的爱情故事
    C#中的explicit和implicit了解一下吧
    通俗易懂,什么是.NET Core以及.NET Core能做什么
    目前下载VS2017你可能会遇到这个坑
    .NET Core中使用Dapper操作Oracle存储过程最佳实践
    分享一个.NET平台开源免费跨平台的大数据分析框架.NET for Apache Spark
    从ASP.NET Core2.2到3.0你可能会遇到这些问题
    What?VS2019创建新项目居然没有.NET Core3.0的模板?Bug?
  • 原文地址:https://www.cnblogs.com/mitsui/p/6826797.html
Copyright © 2011-2022 走看看