多线程(threading module)

Posted horror

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程(threading module)相关的知识,希望对你有一定的参考价值。

一、线程与进程

线程定义:线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

进程定义:An executing instance of a program is called a process.(程序的执行实例称为进程。)

线程与进程的区别:

  1. 线程共享创建它的进程的地址空间; 进程有自己的地址空间。
  2. 线程可以直接访问其进程的数据段; 进程拥有自己父进程数据段的副本。
  3. 线程可以直接与其进程的其他线程通信; 进程必须使用进程间通信来与兄弟进程通信。
  4. 新线程很容易创建; 新流程需要复制父流程。
  5. 线程可以对同一进程的线程进行相当大的控制; 进程只能控制子进程。
  6. 对主线程的更改(取消,优先级更改等)可能会影响进程的其他线程的行为; 对父进程的更改不会影响子进程。

二、Python GIL(Global Interpreter Lock)

  --> 全局解释器锁 :在同一时刻,只能有一个线程进入解释器。

三、threading 模块

3.1 线程的2种调用方式

直接调用

技术分享图片
 1 import threading
 2 import time
 3  
 4 def sayhi(num): #定义每个线程要运行的函数
 5  
 6     print("running on number:%s" %num)
 7  
 8     time.sleep(3)
 9  
10 if __name__ == __main__:
11  
12     t1 = threading.Thread(target=sayhi,args=(1,)) #生成一个线程实例
13     t2 = threading.Thread(target=sayhi,args=(2,)) #生成另一个线程实例
14  
15     t1.start() #启动线程
16     t2.start() #启动另一个线程
17  
18     print(t1.getName()) #获取线程名
19     print(t2.getName())
View Code

继承式调用

技术分享图片
 1 import threading
 2 import time
 3  
 4  
 5 class MyThread(threading.Thread):
 6     def __init__(self,num):
 7         threading.Thread.__init__(self)
 8         self.num = num
 9  
10     def run(self):#定义每个线程要运行的函数
11  
12         print("running on number:%s" %self.num)
13  
14         time.sleep(3)
15  
16 if __name__ == __main__:
17  
18     t1 = MyThread(1)
19     t2 = MyThread(2)
20     t1.start()
21     t2.start()
View Code

3.2 常用方法(Join/Daemon)

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

  setDaemon(True) -->  将线程声明为守护线程,必须在start() 方法调用之前设置,守护线程随主线程结束而结束。

其他方法:

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

3.3 同步锁(Lock)

  r = threading.Lock()  r.acquire() --> 加锁  r.release() --> 解锁

技术分享图片
 1 import time
 2 import threading
 3 
 4 def addNum():
 5     global num #在每个线程中都获取这个全局变量
 6     # num-=1
 7     lock.acquire()
 8     temp=num
 9     print(--get num:,num )
10     #time.sleep(0.1)
11     num =temp-1 #对此公共变量进行-1操作
12     lock.release()
13 
14 num = 100  #设定一个共享变量
15 thread_list = []
16 lock=threading.Lock()
17 
18 for i in range(100):
19     t = threading.Thread(target=addNum)
20     t.start()
21     thread_list.append(t)
22 
23 for t in thread_list: #等待所有线程执行完毕
24     t.join()
25 
26 print(final num:, num )
View Code

3.4 线程死锁和递归锁

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

技术分享图片
 1 import threading,time
 2 
 3 class myThread(threading.Thread):
 4     def doA(self):
 5         lockA.acquire()
 6         print(self.name,"gotlockA",time.ctime())
 7         time.sleep(3)
 8         lockB.acquire()
 9         print(self.name,"gotlockB",time.ctime())
10         lockB.release()
11         lockA.release()
12 
13     def doB(self):
14         lockB.acquire()
15         print(self.name,"gotlockB",time.ctime())
16         time.sleep(2)
17         lockA.acquire()
18         print(self.name,"gotlockA",time.ctime())
19         lockA.release()
20         lockB.release()
21     def run(self):
22         self.doA()
23         self.doB()
24 if __name__=="__main__":
25 
26     lockA=threading.Lock()
27     lockB=threading.Lock()
28     threads=[]
29     for i in range(5):
30         threads.append(myThread())
31     for t in threads:
32         t.start()
33     for t in threads:
34         t.join()
deadLock

解决办法:使用递归锁

  即重新定义一把锁:lock = threading.RLock() --> 递归锁

  将所有的锁替换为递归锁即可。递归锁可以重复加锁。

  RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。

3.5 信号量(Semaphore)--> 相当于一把锁

  信号量用来控制线程并发数的,BoundedSemaphore或Semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1。

  计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。(类似于停车位的概念)

  BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。

技术分享图片
 1 import threading,time
 2 class myThread(threading.Thread):
 3     def run(self):
 4         if semaphore.acquire():
 5             print(self.name)
 6             time.sleep(5)
 7             semaphore.release()
 8 if __name__=="__main__":
 9     semaphore=threading.Semaphore(5)
10     thrs=[]
11     for i in range(100):
12         thrs.append(myThread())
13     for t in thrs:
14         t.start()
Semaphore

3.6 条件变量同步(Condition)--> 锁

  有一类线程需要满足条件之后才能够继续执行,Python提供了threading.Condition 对象用于条件变量线程的支持,它除了能提供RLock()或Lock()的方法外,还提供了 wait()、notify()、notifyAll()方法。

      lock_con=threading.Condition([Lock/Rlock]): 锁是可选选项,不传入锁,对象自动创建一个RLock()。

1 wait():条件不满足时调用,线程会释放锁并进入等待阻塞;
2 notify():条件创造后调用,通知等待池激活一个线程;
3 notifyAll():条件创造后调用,通知等待池激活所有线程。
技术分享图片
 1 import threading,time
 2 from random import randint
 3 class Producer(threading.Thread):
 4     def run(self):
 5         global L
 6         while True:
 7             val=randint(0,100)
 8             print(生产者,self.name,":Append"+str(val),L)
 9             if lock_con.acquire():
10                 L.append(val)
11                 lock_con.notify()
12                 lock_con.release()
13             time.sleep(3)
14 class Consumer(threading.Thread):
15     def run(self):
16         global L
17         while True:
18                 lock_con.acquire()
19                 if len(L)==0:
20                     lock_con.wait()
21                 print(消费者,self.name,":Delete"+str(L[0]),L)
22                 del L[0]
23                 lock_con.release()
24                 time.sleep(0.25)
25 
26 if __name__=="__main__":
27 
28     L=[]
29     lock_con=threading.Condition()
30     threads=[]
31     for i in range(5):
32         threads.append(Producer())
33     threads.append(Consumer())
34     for t in threads:
35         t.start()
36     for t in threads:
37         t.join()
Condition Demo

3.7 同步条件(Event)

条件同步和条件变量同步差不多意思,只是少了锁功能,因为条件同步设计于不访问共享资源的条件环境。event=threading.Event():条件环境对象,初始值 为False;

1 event.isSet():返回event的状态值;
2 
3 event.wait():如果 event.isSet()==False将阻塞线程;
4 
5 event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
6 
7 event.clear():恢复event的状态值为False。

示例:

技术分享图片
 1 import threading,time
 2 class Boss(threading.Thread):
 3     def run(self):
 4         print("BOSS:今晚大家都要加班到22:00。")
 5         event.isSet() or event.set()
 6         time.sleep(5)
 7         print("BOSS:<22:00>可以下班了。")
 8         event.isSet() or event.set()
 9 class Worker(threading.Thread):
10     def run(self):
11         event.wait()
12         print("Worker:哎……命苦啊!")
13         time.sleep(0.25)
14         event.clear()
15         event.wait()
16         print("Worker:OhYeah!")
17 if __name__=="__main__":
18     event=threading.Event()
19     threads=[]
20     for i in range(5):
21         threads.append(Worker())
22     threads.append(Boss())
23     for t in threads:
24         t.start()
25     for t in threads:
26         t.join()
View Code

3.8 队列(queue)-->多线程利器

queue中的方法:

创建一个“队列”对象
import Queue
q = Queue.Queue(maxsize = 10)
Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。

将一个值放入队列中
q.put(10)
调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为
1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。

将一个值从队列中取出
q.get()
调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True,get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。

Python Queue模块有三种队列及构造函数:
1、Python Queue模块的FIFO队列先进先出。  class queue.Queue(maxsize)
2、LIFO类似于堆,即先进后出。             class queue.LifoQueue(maxsize)
3、还有一种是优先级队列级别越低越先出来。   class queue.PriorityQueue(maxsize)

此包中的常用方法(q = Queue.Queue()):
q.qsize() 返回队列的大小
q.empty() 如果队列为空,返回True,反之False
q.full() 如果队列满了,返回True,反之False
q.full 与 maxsize 大小对应
q.get([block[, timeout]]) 获取队列,timeout等待时间
q.get_nowait() 相当q.get(False)
非阻塞 q.put(item) 写入队列,timeout等待时间
q.put_nowait(item) 相当q.put(item, False)
q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
q.join() 实际上意味着等到队列为空,再执行别的操作

示例:

技术分享图片
 1 import threading,queue
 2 from time import sleep
 3 from random import randint
 4 class Production(threading.Thread):
 5     def run(self):
 6         while True:
 7             r=randint(0,100)
 8             q.put(r)
 9             print("生产出来%s号包子"%r)
10             sleep(1)
11 class Proces(threading.Thread):
12     def run(self):
13         while True:
14             re=q.get()
15             print("吃掉%s号包子"%re)
16 if __name__=="__main__":
17     q=queue.Queue(10)
18     threads=[Production(),Production(),Production(),Proces()]
19     for t in threads:
20         t.start()
Demo





以上是关于多线程(threading module)的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程——Lock&Condition

线程同步-使用ReaderWriterLockSlim类

python threading超线程使用简单范例的代码

python的多线程多进程代码示例

C# Thread开启线程的几种方式

java 如何编写多线程的代码