zoukankan      html  css  js  c++  java
  • python的线程

    简简单单的了解一下:

      进程就是一个程序在一个数据集上的一次动态执行过程。也就是程序运行的过程。

      进程是资源管理单位,管理线程的就是进程。

      进程一般由程序、数据集、进程控制块三部分组成:

        我们编写的程序是用来描述进程要完成那些功能以及如何完成;

        数据集则是程序在执行过程中所需要使用的资源;

        进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以通过他来控制和管理进程,他是系统感知进程存在的唯一标志。

      线程是进程中执行运算的最小单位,是进程中的一个实体,是被系统独立调度和分派的基本单位。

      线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。

      一个线程可以创建和撤销,同一进程中的多个线程之间可以并发执行。

      进程与线程的关系:

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

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

          cpu分给线程,即真正在cop上运行的是线程。

      

      python中的GIL(全局解释器锁):

          全局解释器锁(GIL)每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码

          在IO操作可能会引起阻塞的system call之前,可以暂时释放GIL,但在执行完毕后必须重新获取GIL,Python3X使用计时器(执行时间达到阀值后,当前线程释放GIL)或Python2X,tickets计数达到100

          在执行CPU密集型数据时,需要持续使用CPU的任务时,单线程会比多线程快

          在执行IO密集型数据时可能引起阻塞的任务多线程会比单线程快

    嗨嗨皮皮的搞一搞:

      线程的调用:  

    直接调用 

    1
    import threading,time 2 3 def Output(num): 4 print("Output the number:%s"%num) 5 time.sleep(3) 6 7 if __name__ == '__main__': 8 """ 9 使用threading.Thread方法可以创建一个线程 10 target属性是用来指定执行的动作 11 args属性是用来以元组的方式给执行动作传递参数 12 """ 13 t1 = threading.Thread(target=Output,args=(1,)) # 创建一个线程实例 14 t2 = threading.Thread(target=Output,args=(2,)) # 创建另一个线程实例 15 16 t1.start() # 启动线程 17 t2.start() # 启动另一个线程 18 19 print(t1.getName()) # 获取线程名 20 print(t2.getName())

    》》》Output the number:1
       Output the number:2
       Thread-1
       Thread-2
     继承式调用:

    1
    import threading,time 2 3 class MyThread(threading.Thread): 4 def __init__(self,num): 5 threading.Thread.__init__(self) 6 self.num = num 7 8 def run(self): 9 print("Output the number:%s"%self.num) 10 time.sleep(3) 11 12 if __name__ == '__main__': 13 t1 = MyThread(11) 14 t2 = MyThread(22) 15 t1.start() 16 t2.start()

    》》》Output the number:11
       Output the number:22

      thread 模块提供的方法:

     1 import threading
     2 from time import ctime,sleep
     3 
     4 def music(func):
     5     for i in range(2):
     6         print("begin listening to %s   [%s]"%(func,ctime()))
     7         sleep(2)
     8         print("end listening [%s]"%ctime())
     9 
    10 def move(func):
    11     for i in range(2):
    12         print("Begin watching at the %s   [%s]"%(func,ctime()))
    13         sleep(5)
    14         print("end watching [%s]"%ctime())
    15 
    16 threads = []
    17 t1 = threading.Thread(target=music,args=('小苹果',)) # 子线程
    18 threads.append(t1)
    19 t2 = threading.Thread(target=move,args=('还珠格格',)) # 子线程
    20 threads.append(t2)
    21 
    22 if __name__ == '__main__':
    23     for i in threads:
    24         # i.setDaemon(True) # 守护线程
    25         i.start()
    26         # i.join() # 在子线程完成运行前,这个子线程的父线程将一直被阻塞
    27 
    28     print("all over [%s]"%ctime()) # 父线程

    》》》begin listening to 小苹果   [Sat Oct 13 12:44:40 2018]
       Begin watching at the 还珠格格   [Sat Oct 13 12:44:40 2018]
       all over [Sat Oct 13 12:44:40 2018]
       end listening [Sat Oct 13 12:44:42 2018]
       begin listening to 小苹果   [Sat Oct 13 12:44:42 2018]
       end listening [Sat Oct 13 12:44:44 2018]
       end watching [Sat Oct 13 12:44:45 2018]
       Begin watching at the 还珠格格   [Sat Oct 13 12:44:45 2018]
       end watching [Sat Oct 13 12:44:50 2018]
    join()方法:等待至线程中止。 

    1
    import threading 2 from time import ctime,sleep 3 4 def music(func): 5 for i in range(2): 6 print("begin listening to %s [%s]"%(func,ctime())) 7 sleep(2) 8 print("end listening [%s]"%ctime()) 9 10 def move(func): 11 for i in range(2): 12 print("Begin watching at the %s [%s]"%(func,ctime())) 13 sleep(5) 14 print("end watching [%s]"%ctime()) 15 16 threads = [] 17 t1 = threading.Thread(target=music,args=('小苹果',)) # 子线程 18 threads.append(t1) 19 t2 = threading.Thread(target=move,args=('还珠格格',)) # 子线程 20 threads.append(t2) 21 22 if __name__ == '__main__': 23 for i in threads: 24 # i.setDaemon(True) # 守护线程 25 i.start() 26 i.join() # 在子线程完成运行前,这个子线程的父线程将一直被阻塞 27 28 print("all over [%s]"%ctime()) # 父线程

    》》》begin listening to 小苹果   [Sat Oct 13 12:45:46 2018]
       end listening [Sat Oct 13 12:45:48 2018]
       begin listening to 小苹果   [Sat Oct 13 12:45:48 2018]
       end listening [Sat Oct 13 12:45:50 2018]
       Begin watching at the 还珠格格   [Sat Oct 13 12:45:50 2018]
       end watching [Sat Oct 13 12:45:55 2018]
       Begin watching at the 还珠格格   [Sat Oct 13 12:45:55 2018]
       end watching [Sat Oct 13 12:46:00 2018]
       all over [Sat Oct 13 12:46:00 2018]
    setDaemon(True)方法:守护线程

    1
    ''' 2 将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。 3 这个方法基本和join是相反的。当我们 在程序运行中,执行一个主线程,如果主线程又创建一个子线程, 4 主线程和子线程 就分兵两路,分别运行,那么当主线程完成想退出时,会检验子线程是否完成。 5 如 果子线程未完成,则主线程会等待子线程完成后再退出。但是有时候我们需要的是 只要主线程完成了, 6 不管子线程是否完成,都要和主线程一起退出,这时就可以 用setDaemon方法啦 7 ''' 8 import threading 9 from time import ctime,sleep 10 11 def music(func): 12 for i in range(2): 13 print("begin listening to %s [%s]"%(func,ctime())) 14 sleep(2) 15 print("end listening [%s]"%ctime()) 16 17 def move(func): 18 for i in range(2): 19 print("Begin watching at the %s [%s]"%(func,ctime())) 20 sleep(5) 21 print("end watching [%s]"%ctime()) 22 23 threads = [] 24 t1 = threading.Thread(target=music,args=('小苹果',)) # 子线程 25 threads.append(t1) 26 t2 = threading.Thread(target=move,args=('还珠格格',)) # 子线程 27 threads.append(t2) 28 29 if __name__ == '__main__': 30 for i in threads: 31 i.setDaemon(True) # 守护线程 32 i.start() 33 # i.join() # 在子线程完成运行前,这个子线程的父线程将一直被阻塞 34 35 print("all over [%s]"%ctime()) # 父线程

    》》》begin listening to 小苹果   [Sat Oct 13 12:49:17 2018]
       Begin watching at the 还珠格格   [Sat Oct 13 12:49:17 2018]
       all over [Sat Oct 13 12:49:17 2018]
     1  threading.currentThread(): 返回当前的线程变量。
     2  threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
     3  threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
     4 
     5  除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:
     6  run(): 用以表示线程活动的方法。
     7  start():启动线程活动。
     8  join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
     9  isAlive(): 返回线程是否活动的。
    10  getName(): 返回线程名。
    11  setName(): 设置线程名。
    其他方法

      同步锁:

     1 import threading,time
     2 
     3 def AddNum():
     4     global num
     5 
     6     num-=1
     7     # temp = num
     8     # time.sleep(0.001)
     9     # num=temp-1
    10 
    11 
    12 num = 100    #设定一个共享变量
    13 tread_list=[]
    14 for i in range(100):
    15     t = threading.Thread(target=AddNum)
    16     t.start()
    17     tread_list.append(t)
    18 
    19 for t in tread_list: # 等待所有线程执行完毕后再数据最后的结果
    20     t.join()
    21 
    22 print("final num:%s"%num)

    》》》final num:0
     1 # Author : Adair
     2 # -*- coding:utf-8 -*-
     3 
     4 import threading,time
     5 
     6 def AddNum():
     7     global num
     8 
     9     # num-=1
    10     temp = num
    11     time.sleep(0.001)
    12     num=temp-1
    13 
    14 
    15 num = 100    #设定一个共享变量
    16 tread_list=[]
    17 for i in range(100):
    18     t = threading.Thread(target=AddNum)
    19     t.start()
    20     tread_list.append(t)
    21 
    22 for t in tread_list: # 等待所有线程执行完毕后再数据最后的结果
    23     t.join()
    24 
    25 print("final num:%s"%num)

    》》》final num:91

    为什么num-=1时结果没问题,而用sleep将计算过程分开结果就会出现问题?
        1、因为num-=时动作太快,全局解释器锁(GIL)还没到线程切换的时间,这一个计算过程是在同一次计算过程中完成。
        2、sleep明显时间超过了线程的切换时间,100个线程每一个一定都没执行完就进行了切换,sleep相当于IO阻塞,在阻塞时间内不会被切换回来,所以前90多次中线程每次取的num值都是100,因为前一个线程根本就没计算完 

      遇到这样的问题,我们可以使用同步锁的机制来锁定每次计算过程。当然也可以使用join方法,但join会把整个线程给停住,会让多线程失去意义。

      同步锁:

     1 import threading,time
     2 
     3 def AddNum():
     4     global num
     5 
     6     lock.acquire() # 获取一个锁
     7     temp = num
     8     time.sleep(1)
     9     num=temp-1
    10     lock.release() # 释放这个锁
    11 
    12 
    13 num = 100    #设定一个共享变量
    14 tread_list=[]
    15 lock=threading.Lock() # 调用进程锁的方法
    16 
    17 for i in range(100):
    18     t = threading.Thread(target=AddNum)
    19     t.start()
    20     tread_list.append(t)
    21 
    22 for t in tread_list: # 等待所有线程执行完毕后再数据最后的结果
    23     t.join()
    24 
    25 print("final num:%s"%num)

    》》》final num:0
    同步锁与GIL的关系?
    
        Python的线程在GIL的控制之下,线程之间,对整个python解释器,对python提供的C API的访问都是互斥的,这可以看作是Python内核级的互斥机制。但是这种互斥是我们不能控制的,我们还需要另外一种可控的互斥机制———用户级互斥。内核级通过互斥保护了内核的共享资源,同样,用户级互斥保护了用户程序中的共享资源。
        GIL 的作用是:对于一个解释器,只能有一个thread在执行bytecode。所以每时每刻只有一条bytecode在被执行一个thread。GIL保证了bytecode 这层面上是thread safe的。
    但是如果你有个操作比如 x += 1,这个操作需要多个bytecodes操作,在执行这个操作的多条bytecodes期间的时候可能中途就换thread了,这样就出现了data races的情况了。
     
        那我的同步锁也是保证同一时刻只有一个线程被执行,是不是没有GIL也可以?是的;那要GIL有什么鸟用?你没治;

     线程的死锁与递归锁:

      在线程之间如果存在多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都在正在使用,所以这两个线程在无外力作用下将一直等待下去:

     1 import threading, time
     2 
     3 
     4 class MyThread(threading.Thread):
     5     def doA(self):
     6         lockA.acquire() # 获取第一把锁
     7         print(self.name, "doA--1", time.ctime())
     8         time.sleep(3)
     9         lockB.acquire() # 获取第二把锁
    10         print(self.name, "doA--2", time.ctime())
    11         lockB.release() # 释放第二把锁
    12         lockA.release() # 释放第一把锁
    13 
    14     def doB(self):
    15         lockB.acquire() # 获取第二把锁
    16         print(self.name, "doB--1", time.ctime())
    17         time.sleep(2)
    18         lockA.acquire() # 获取第一把锁
    19         print(self.name, "doB--2", time.ctime())
    20         lockA.release() # 释放第一把锁
    21         lockB.release() # 释放第二把锁
    22 
    23     def run(self):
    24         self.doA()
    25         self.doB()
    26 
    27 
    28 if __name__ == "__main__":
    29     lockA = threading.Lock()  # 创建第一个锁
    30     lockB = threading.Lock()  # 创建第二个锁
    31 
    32     threads = []
    33     for i in range(5):  # 创建5个线程
    34         threads.append(MyThread())
    35 
    36     for t in threads:
    37         t.start()  # 启动线程
    38 
    39     for t in threads:
    40         t.join()  # 等待线程结束

    》》》Thread-1 doA--1 Tue Oct 23 23:27:46 2018
      Thread-1 doA--2 Tue Oct 23 23:27:49 2018
      Thread-1 doB--1 Tue Oct 23 23:27:49 2018
      Thread-2 doA--1 Tue Oct 23 23:27:49 2018
      ....(出现死锁现象,程序一直等待)

    为了支持在同一线程中多次请求同一资源,python提供了“可重用锁”也叫“递归锁”(threading.Rlock)。Rlock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有 acquire都被release,其它的线程才能获得资源。

     1 import threading, time
     2 
     3 
     4 class MyThread(threading.Thread):
     5     def doA(self):
     6         lock.acquire() # 获取第一把重用锁
     7         print(self.name, "doA--1", time.ctime())
     8         time.sleep(3)
     9         lock.acquire() # 获取第一把重用锁
    10         print(self.name, "doA--2", time.ctime())
    11         lock.release() # 释放一把重用锁
    12         lock.release() # 释放一把重用锁
    13 
    14     def doB(self):
    15         lock.acquire() # 获取第一把重用锁
    16         print(self.name, "doB--1", time.ctime())
    17         time.sleep(2)
    18         lock.acquire() # 获取第一把重用锁
    19         print(self.name, "doB--2", time.ctime())
    20         lock.release() # 释放一把重用锁
    21         lock.release() # 释放一把重用锁
    22 
    23     def run(self):
    24         self.doA()
    25         self.doB()
    26 
    27 
    28 if __name__ == "__main__":
    29     # lockA = threading.Lock()  # 创建第一个锁
    30     # lockB = threading.Lock()  # 创建第二个锁
    31     lock=threading.RLock()  # 创建重用锁/递归锁
    32 
    33     threads = []
    34     for i in range(5):  # 创建5个线程
    35         threads.append(MyThread())
    36 
    37     for t in threads:
    38         t.start()  # 启动线程
    39 
    40     for t in threads:
    41         t.join()  # 等待线程结束

     信号量:

      信号量是用来控制并发数的,信号量中管理这一个内置的计数器,没当调用acquire()时计数器上-1,调用release()时+1.

      注意计数器不能小于 0,当计数器为0时,acquire()将阻塞线程至同步状态,直到其他线程调用release()

     1 import threading,time
     2 
     3 class MyThread(threading.Thread):
     4 
     5     def __init__(self,num):
     6         threading.Thread.__init__(self)
     7         self.num = num
     8 
     9     def run(self):
    10         semaphore.acquire()
    11         print(self.name,self.num)
    12         time.sleep(2)
    13         semaphore.release()
    14 
    15 
    16 if __name__ == "__main__":
    17     Threads = []
    18     semaphore=threading.BoundedSemaphore(3)
    19 
    20     for t in range(10):
    21         Threads.append(MyThread(t))
    22     for t in Threads:
    23         t.start()

      举个栗子:

        信号量就好比停车场,而threading.BoundedSemaphore(3)就好比设置了停车场里只有3个车位,停车场一次最多只能停三辆车,其他车辆只能在外等待。当有一辆车离开停车场后等待的一辆车才可以进入停车场。依次循环。

        信号量一般用于对数据库连接数的限制

    条件变量同步:

      python还提供了threading.Condition()对象用于条件变量线程的支持,设置线程满一定足条件后才能够继续执行。

      创建条件变量同步锁:lock=threading.Condition() 括号中需要加载锁类型(Lock/Rlock),若括号中什么都不填则默认为Rlock()

      

      方法:

        wait() :条件不满足时调用,线程会释放锁并进入等待阻塞

        notify():条件创造后调用,通知等待池激活一个线程

        notifyAll():条件创造后调用,通知等待池激活所有线程

     1 import threading,time,random
     2 
     3 class producers(threading.Thread):
     4     global ProductContainers
     5     def run(self):
     6         while True:
     7             lock.acquire()
     8             ProductNum = random.randint(0,10)
     9             ProductContainers.append(ProductNum)
    10             print("产品集中有:",ProductContainers)
    11             lock.notify()
    12             lock.release()
    13             time.sleep(2)
    14 
    15 class consumers(threading.Thread):
    16     global ProductContainers
    17     def run(self):
    18         while True:
    19             lock.acquire()
    20             if len(ProductContainers) == 0:
    21                 lock.wait()
    22             print("线程 %s 消费了产品 %d"%(self.name,ProductContainers[0]))
    23             del ProductContainers[0]
    24             lock.release()
    25             time.sleep(1)
    26 
    27 
    28 
    29 if __name__ == "__main__":
    30     ProductContainers=[]
    31     lock = threading.Condition()
    32     threads=[]
    33 
    34 
    35     for t in range(3):
    36         threads.append(producers())
    37     for t in range(4):
    38         threads.append(consumers())
    39 
    40     for t in threads:
    41         t.start()
    42 
    43     for t in threads:
    44         t.join()

     同步条件:

      条件同步和条件变量同步差不多,只是少了锁的功能,因为条件同步设计与不访问共享资源的条件环境。

    event=threading.Event()  #条件环境对象,初始值为False
    event.isSet() # 返回event的状态值
    event.wait() # 如果event.isSet()==False将线程阻塞
    event.set() # 设置even的状态为True,所有阻塞池的线程激活进入就绪状态,等待操作系统的调度
    event.clear() # 恢复event的状态值为False
     1 import threading,time
     2 
     3 class Boos(threading.Thread):
     4     def run(self):
     5         print("Boos:今天大家加班到22:00!",event.isSet())
     6         event.isSet() or event.set()
     7         print("Boos",event.isSet())
     8         time.sleep(1) # 等待时线程进行切换
     9         print("Boos:哪算了,下班吧!",event.isSet())
    10         event.isSet() or event.set()
    11 
    12 class Worker(threading.Thread):
    13     def run(self):
    14         print("Worker",event.isSet())
    15         event.wait()
    16         print("Worker:哎呦,卧槽!")
    17         event.clear()
    18         event.wait()
    19         print("Worker:搜嘎!")
    20 
    21 
    22 if __name__ == "__main__":
    23     Treads=[] # 列表是有序的
    24     event=threading.Event()
    25 
    26     for w in range(5):
    27         Treads.append(Worker())
    28     Treads.append(Boos())
    29 
    30     for t in Treads:
    31         t.start()
  • 相关阅读:
    PHP的几个常用加密函数
    sha1() 函数
    微信支付接口文档
    最强PostMan使用教程(1)
    用 Graphviz画神经网络图
    绘图工具graphviz学习使用
    maven添加oracle jdbc依赖
    httrack,webdup,WinHTTrack,WebZip
    过滤ASCII码中的不可见字符, ASCII三部分, 各控制字符详解, 去^@,^M
    报LinkageError的原因
  • 原文地址:https://www.cnblogs.com/Adairye/p/9768228.html
Copyright © 2011-2022 走看看