进程与线程

操作系统

1.为什么要有操作系统
操作系统,位于底层硬件与应用软件之间的一层
工作方式:向下管理硬件,向上提供接口

操作系统进程切换:
1.出现IO操作
2.固定时间

进程

1.定义

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

 

进程由三部分组成:

1、程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成

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

3、进程控制块:进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感

     知进程存在的唯一标志。

线程

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

Threading用于提供线程相关的操作。线程是应用程序中工作的最小单元,它被包含在进程之中,是进程中的实际运作单位。一

条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

 

 

 

进程与线程的关系

进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。或者说进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

进程和线程的关系:

(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
(3)CPU分给线程,即真正在CPU上运行的是线程。

 进程: 资源管理单位(容器)

 线程: 最小执行单位

 

 并行与并发

 

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

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

 

 同步与异步

 在计算机领域,同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。举个例子,打电话时就是同步通信,发短息时就是异步通信。

 

1. 实现线程并发

 示例1:

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*- 
 3
 4 
 5 import threading  #线程
 6 import time
 7 
 8 def Hi(num): #有一个参数
 9     print("hello %s" %num)
10     time.sleep(3)  
11 
12 if __name__ == '__main__':
13 
14     t1=threading.Thread(target=Hi,args=(10,))  #创建了一个线程对象t1,10做为一个参数,传给num
15     t1.start()
16 
17     t2=threading.Thread(target=Hi,args=(9,))   #创建了一个线程对象t2,9做为一个参数,传给num
18     t2.start()
19 
20     print("ending.........")  #主线程输出ending

执行结果:

1 hello 10    #子线程
2 hello 9     #子线程
3 ending.........   #主线程
4 #上面三个同时出来,再停顿三秒才结束
5 Process finished with exit code 0  #停顿3秒才结束

示例2:

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*- 
 3 
 4 
 5 import threading
 6 import time
 7 
 8 def music():
 9     print("begin to listen %s"%time.ctime())
10     time.sleep(3)
11     print("stop to listen %s" %time.ctime())
12 
13 def game():
14     print("begin to play game %s"%time.ctime())
15     time.sleep(5)
16     print("stop to play game %s" %time.ctime())
17 
18 if __name__ == '__main__':
19 
20     t1=threading.Thread(target=music)
21     t1.start()
22     t2=threading.Thread(target=game)
23     t2.start()

 

执行结果:

1 #总共花了5秒时间
2 
3 begin to listen Sat Jan 14 12:34:43 2017
4 begin to play game Sat Jan 14 12:34:43 2017  #1、先打印2个
5 
6 stop to listen Sat Jan 14 12:34:46 2017      #2、等待3秒再打印一个
7 
8 stop to play game Sat Jan 14 12:34:48 2017   #3、再等待2秒,打印一个

 

 2.使用 join方法

示例1:

1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*- 
 3 
 4 
 5 import threading
 6 import time
 7 
 8 def music():
 9     print("begin to listen %s"%time.ctime())
10     time.sleep(3)
11     print("stop to listen %s" %time.ctime())
12 
13 def game():
14     print("begin to play game %s"%time.ctime())
15     time.sleep(5)
16     print("stop to play game %s" %time.ctime())
17 
18 if __name__ == '__main__':
19 
20     t1=threading.Thread(target=music)
21     t2=threading.Thread(target=game)
22 
23     t1.start()  #运行实例的方法
24     t2.start()
25 
26     t1.join()   #子线程对象调用join()方法
27     t2.join()
28 
29     print("ending")  #在主线程中

 

 执行结果:

1 begin to listen Sat Jan 14 12:58:34 2017
2 begin to play game Sat Jan 14 12:58:34 2017  #先打印2个
3 
4 stop to listen Sat Jan 14 12:58:37 2017      #等待3秒,再打印一个
5 
6 stop to play game Sat Jan 14 12:58:39 2017   #等待2秒,再打印两个
7 ending  

示例2:

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*- 
 3 
 4 
 5 import threading
 6 import time
 7 
 8 def music():
 9     print("begin to listen %s"%time.ctime())
10     time.sleep(3)
11     print("stop to listen %s" %time.ctime())
12 
13 def game():
14     print("begin to play game %s"%time.ctime())
15     time.sleep(5)
16     print("stop to play game %s" %time.ctime())
17 
18 if __name__ == '__main__':
19 
20     t1=threading.Thread(target=music)
21     t2=threading.Thread(target=game)
22 
23     t1.start()  #运行实例的方法
24     t2.start()
25 
26     t1.join()   #t1线程不结束,谁都不往下走
27 
28     print("ending")  

执行结果:

1 begin to listen Sat Jan 14 13:06:07 2017
2 begin to play game Sat Jan 14 13:06:07 2017  #先打印这两行
3 
4 stop to listen Sat Jan 14 13:06:10 2017      #再等待3秒打印这两行
5 ending
6 
7 stop to play game Sat Jan 14 13:06:12 2017   #再等待2秒打印这行

示例3:

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*- 
 3 
 4 
 5 import threading
 6 import time
 7 
 8 def music():
 9     print("begin to listen %s"%time.ctime())
10     time.sleep(3)
11     print("stop to listen %s" %time.ctime())
12 
13 def game():
14     print("begin to play game %s"%time.ctime())
15     time.sleep(5)
16     print("stop to play game %s" %time.ctime())
17 
18 if __name__ == '__main__':
19 
20     t1=threading.Thread(target=music)
21     t2=threading.Thread(target=game)
22 
23     t1.start()  #运行实例的方法
24     t2.start()
25 
26     t2.join()
27 
28     print("ending")  #在主线程中

执行结果:

1 begin to listen Sat Jan 14 13:12:34 2017     #先打印这两行
2 begin to play game Sat Jan 14 13:12:34 2017
3 
4 stop to listen Sat Jan 14 13:12:37 2017      #等待3秒,打印这一行
5 
6 stop to play game Sat Jan 14 13:12:39 2017   #等待2秒,打印这两行
7 ending

示例4: 并没有实现并发(实现多线程的意义)

 

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*- 
 3 
 4 
 5 import threading
 6 import time
 7 
 8 def music():
 9     print("begin to listen %s"%time.ctime())
10     time.sleep(3)
11     print("stop to listen %s" %time.ctime())
12 
13 def game():
14     print("begin to play game %s"%time.ctime())
15     time.sleep(5)
16     print("stop to play game %s" %time.ctime())
17 
18 if __name__ == '__main__':
19 
20     t1=threading.Thread(target=music)
21     t2=threading.Thread(target=game)
22 
23     t1.start()
24 
25     t1.join()
26     t2.start()
27     
28     t2.join()
29 
30     print("ending")  #在主线程中

执行结果:

1 begin to listen Sat Jan 14 13:26:18 2017    #先打印条1行
2 
3 stop to listen Sat Jan 14 13:26:21 2017     #等待3秒再打印2行
4 begin to play game Sat Jan 14 13:26:21 2017
5 
6 stop to play game Sat Jan 14 13:26:26 2017  #等待5秒打印2行
7 ending

  

 线程调用方法:

1.直接调用

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*- 
 3 
 4 
 5 import threading
 6 import time
 7 
 8 
 9 def sayhi(num):  # 定义每个线程要运行的函数
10 
11     print("running on number:%s" % num)
12 
13     time.sleep(3)
14 
15 
16 if __name__ == '__main__':
17     t1 = threading.Thread(target=sayhi, args=(1,))  # 生成一个线程实例
18     t2 = threading.Thread(target=sayhi, args=(2,))  # 生成另一个线程实例
19 
20     t1.start()  # 启动线程
21     t2.start()  # 启动另一个线程
22 
23     print(t1.getName())  # 获取线程名
24     print(t2.getName())

执行结果:

1 running on number:1
2 running on number:2
3 Thread-1
4 Thread-2

  

 2.继承式调用

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*- 
 3 
 4 
 5 import threading
 6 import time
 7 
 8 #自己定制一个MyThread的类
 9 class MyThread(threading.Thread):  
10     def __init__(self, num):
11         threading.Thread.__init__(self)
12         self.num = num
13 
14     def run(self):  # 定义每个线程要运行的函数
15 
16         print("running on number:%s" % self.num)
17 
18         time.sleep(3)
19 
20 
21 if __name__ == '__main__':
22     t1 = MyThread(1)  #继承这个类,把1这个参数,传给num ,t1就是个线程对象
23     t2 = MyThread(2)
24     t1.start()
25     t2.start()
26 
27     print("ending......")

执行结果:

1 running on number:1
2 running on number:2
3 ending......

  

 用Daemon方法示例(设置t为守护线程,就是字线程,跟着主线程一起推出)

   daemon: 程序直到不存在非守护线程时退出。

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 
 5 import threading
 6 from time import ctime,sleep
 7 import time
 8 
 9 def ListenMusic(name):
10 
11         print ("Begin listening to %s. %s" %(name,ctime()))
12         sleep(3)
13         print("end listening %s"%ctime())
14 
15 def RecordBlog(title):
16 
17         print ("Begin recording the %s! %s" %(title,ctime()))
18         sleep(5)
19         print('end recording %s'%ctime())
20 
21 #创建一个列表,把t1和t2加到列表中去
22 threads = []
23 t1 = threading.Thread(target=ListenMusic,args=('水手',))
24 t2 = threading.Thread(target=RecordBlog,args=('python线程',))
25 threads.append(t1)
26 threads.append(t2)
27 
28 if __name__ == '__main__':
29 
30     for t in threads:
31         t.setDaemon(True) #设置t为守护线程; 注意:一定在start()之前设置,否则会报错
32 
33         t.start()
34 
35     print ("all over %s" %ctime())

 

 执行结果:

1 Begin listening to 水手. Sat Jan 14 13:51:30 2017    #三个同时打印出来
2 Begin recording the python线程! Sat Jan 14 13:51:30 2017
3 all over Sat Jan 14 13:51:30 2017

  

示例3: 设置t1为守护线程,没有意义,达不到效果,因为t2还会继续执行

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 
 5 import threading
 6 from time import ctime,sleep
 7 import time
 8 
 9 def ListenMusic(name):
10 
11         print ("Begin listening to %s. %s" %(name,ctime()))
12         sleep(3)
13         print("end listening %s"%ctime())
14 
15 def RecordBlog(title):
16 
17         print ("Begin recording the %s! %s" %(title,ctime()))
18         sleep(5)
19         print('end recording %s'%ctime())
20 
21 #创建一个列表,把t1和t2加到列表中去
22 threads = []
23 t1 = threading.Thread(target=ListenMusic,args=('水手',))
24 t2 = threading.Thread(target=RecordBlog,args=('python线程',))
25 threads.append(t1)
26 threads.append(t2)
27 
28 if __name__ == '__main__':
29 
30     t1.setDaemon(True)  #设置t1为守护线程; 注意:一定在start之前设置,否则会报错
31     for t in threads:
32 
33         t.start()
34 
35     print ("all over %s" %ctime())

 

执行结果:

1 Begin listening to 水手. Sat Jan 14 14:02:07 2017
2 Begin recording the python线程! Sat Jan 14 14:02:07 2017
3 all over Sat Jan 14 14:02:07 2017          #设置t1为守护线程,所以会先把这三条先打印出来
4 
5 end listening Sat Jan 14 14:02:10 2017     #再等待3秒打印t2,
6 
7 end recording Sat Jan 14 14:02:12 2017     #再等待3秒打印这条出来

 

示例4:设置t2为守护线程,子线程才会跟着主线程一起退出

 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3
 4 
 5 import threading
 6 from time import ctime,sleep
 7 import time
 8 
 9 def ListenMusic(name):
10 
11         print ("Begin listening to %s. %s" %(name,ctime()))
12         sleep(3)
13         print("end listening %s"%ctime())
14 
15 def RecordBlog(title):
16 
17         print ("Begin recording the %s! %s" %(title,ctime()))
18         sleep(5)
19         print('end recording %s'%ctime())
20 
21 #创建一个列表,把t1和t2加到列表中去
22 threads = []
23 t1 = threading.Thread(target=ListenMusic,args=('水手',))
24 t2 = threading.Thread(target=RecordBlog,args=('python线程',))
25 threads.append(t1)
26 threads.append(t2)
27 
28 if __name__ == '__main__':
29 
30     t2.setDaemon(True)  # 设置t2为守护线程; 注意:一定在start之前设置,否则会报错
31     for t in threads:
32 
33         t.start()
34 
35     print ("all over %s" %ctime())

执行结果:

1 Begin listening to 水手. Sat Jan 14 14:17:09 2017
2 Begin recording the python线程! Sat Jan 14 14:17:09 2017
3 all over Sat Jan 14 14:17:09 2017       #先打印这三条
4 
5 end listening Sat Jan 14 14:17:12 2017  #等待3秒,再打印这条;t1结束后,主线程也结束了。

 

其他方法:

Thread实例对象的方法
# isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
 1 #!/usr/bin/env python
 2 # -*- coding:utf-8 -*-
 3 
 4 
 5 import threading
 6 from time import ctime,sleep
 7 import time
 8 
 9 def ListenMusic(name):
10 
11         print ("Begin listening to %s. %s" %(name,ctime()))
12         sleep(3)
13         print("end listening %s"%ctime())
14 
15 def RecordBlog(title):
16 
17         print ("Begin recording the %s! %s" %(title,ctime()))
18         sleep(5)
19         print('end recording %s'%ctime())
20 
21 #创建一个列表,把t1和t2加到列表中去
22 threads = []
23 t1 = threading.Thread(target=ListenMusic,args=('水手',))
24 t2 = threading.Thread(target=RecordBlog,args=('python线程',))
25 threads.append(t1)
26 threads.append(t2)
27 
28 if __name__ == '__main__':
29 
30     t2.setDaemon(True)  # 设置t为守护进程; 注意:一定在start之前设置,否则会报错
31     for t in threads:
32         t.start()
33         print(t.getName())    #返回线程名称:Thread-1
34 
35     print ("all over %s" %ctime())

 

 

GIL(全局解释器锁)

无论你启多少个线程,你有多少个cpu, Python在执行的时候会淡定的在同一时刻只允许一个线程运行。

 

同步锁

 

1.不加锁(拿到的值是不固定的)

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

2.加锁(互斥锁,就是把多线程变成串行,结果不会变)

 1 import time
 2 import threading
 3 
 4 def subNum():
 5     global num
 6     lock.acquire()
 7     temp=num
 8     time.sleep(0.01)
 9     num =temp-1  #对此公共变量进行-1操作
10     lock.release()
11 
12 num = 100  #设定一个共享变量
13 thread_list = []
14 lock=threading.Lock()
15 for i in range(100):
16     t = threading.Thread(target=subNum)
17     t.start()
18     thread_list.append(t)
19 
20 for t in thread_list: #等待所有线程执行完毕
21     t.join()
22 print('Result: ',num)

 

死锁和递归锁

 

 所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

死锁:

 1 import threading
 2 import time
 3 
 4 mutexA = threading.Lock()
 5 mutexB = threading.Lock()
 6 
 7 class MyThread(threading.Thread):
 8 
 9     def __init__(self):
10         threading.Thread.__init__(self)
11 
12     def run(self):
13         self.fun1()
14         self.fun2()
15 
16     def fun1(self):
17 
18         mutexA.acquire()  # 如果锁被占用,则阻塞在这里,等待锁的释放
19 
20         print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
21 
22         mutexB.acquire()
23         print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
24         mutexB.release()
25         mutexA.release()
26 
27 
28     def fun2(self):
29 
30         mutexB.acquire()
31         print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
32         time.sleep(0.2)
33 
34         mutexA.acquire()
35         print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
36         mutexA.release()
37 
38         mutexB.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()

 

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

递归锁:

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

 

Event对象

 

线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其 他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就 会变得非常棘手。为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在 初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行

 

event.isSet():返回event的状态值;

event.wait():如果 event.isSet()==False将阻塞线程;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。

 

 

 可以考虑一种应用场景(仅仅作为说明),例如,我们有多个线程从Redis队列中读取数据来处理,这些线程都要尝试去连接Redis的服务,一般情况下,如果Redis连接不成功,在各个线程的代码中,都会去尝试重新连接。如果我们想要在启动时确保Redis服务正常,才让那些工作线程去连接Redis服务器,那么我们就可以采用threading.Event机制来协调各个工作线程的连接操作:主线程中会去尝试连接Redis服务,如果正常的话,触发事件,各工作线程会尝试连接Redis服务。

 

 

 1 import threading
 2 import time
 3 import logging
 4 
 5 logging.basicConfig(level=logging.DEBUG, format='(%(threadName)-10s) %(message)s',)
 6 
 7 def worker(event):
 8     logging.debug('Waiting for redis ready...')
 9     event.wait()
10     logging.debug('redis ready, and connect to redis server and do some work [%s]', time.ctime())
11     time.sleep(1)
12 
13 def main():
14     readis_ready = threading.Event()
15     t1 = threading.Thread(target=worker, args=(readis_ready,), name='t1')
16     t1.start()
17 
18     t2 = threading.Thread(target=worker, args=(readis_ready,), name='t2')
19     t2.start()
20 
21     logging.debug('first of all, check redis server, make sure it is OK, and then trigger the redis ready event')
22     time.sleep(3) # simulate the check progress
23     readis_ready.set()
24 
25 if __name__=="__main__":
26     main()

threading.Event的wait方法还接受一个超时参数,默认情况下如果事件一致没有发生,wait方法会一直阻塞下去,而加入这个超时参数之后,如果阻塞时间超过这个参数设定的值之后,wait方法会返回。对应于上面的应用场景,如果Redis服务器一致没有启动,我们希望子线程能够打印一些日志来不断地提醒我们当前没有一个可以连接的Redis服务,我们就可以通过设置这个超时参数来达成这样的目的:

复制代码
def worker(event):
    while not event.is_set():
        logging.debug('Waiting for redis ready...')
        event.wait(2)
    logging.debug('redis ready, and connect to redis server and do some work [%s]', time.ctime())
    time.sleep(1)
复制代码

这样,我们就可以在等待Redis服务启动的同时,看到工作线程里正在等待的情况。

 

 Semaphore(信号量)

 

 

Semaphore管理一个内置的计数器,
每当调用acquire()时内置计数器-1;
调用release() 时内置计数器+1;
计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()。

实例:(同时只有5个线程可以获得semaphore,即可以限制最大连接数为5):

 1 import threading
 2 import time
 3 
 4 semaphore = threading.Semaphore(5)
 5 
 6 def func():
 7     if semaphore.acquire():
 8         print (threading.currentThread().getName() + ' get semaphore') #当前运行线程.获取线程名
 9         time.sleep(2)
10         semaphore.release()
11 
12 for i in range(20):
13   t1 = threading.Thread(target=func)
14   t1.start()

 

队列

 

get和put方法

创建一个“队列”对象

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异常。

 

 1 Python Queue模块有三种队列及构造函数: 
 2 
 3 1、Python Queue模块的FIFO队列先进先出。  class queue.Queue(maxsize) 
 4 2、LIFO类似于堆,即先进后出。           class queue.LifoQueue(maxsize) 
 5 3、还有一种是优先级队列级别越低越先出来。 class queue.PriorityQueue(maxsize) 
 6 
 7 
 8 import queue
 9 
10 #先进后出
11 
12 q=queue.LifoQueue()
13 
14 q.put(34)
15 q.put(56)
16 q.put(12)
17 
18 #优先级
19 q=queue.PriorityQueue()
20 q.put([5,100])
21 q.put([7,200])
22 q.put([3,"hello"])
23 q.put([4,{"name":"alex"}])
24 
25 while 1:
26   data=q.get()
27   print(data)

 

join和task_done方法

 1 join() 阻塞进程,直到所有任务完成,需要配合另一个方法task_done。
 2 
 3     def join(self):
 4      with self.all_tasks_done:
 5       while self.unfinished_tasks:
 6        self.all_tasks_done.wait()
 7 
 8 task_done() 表示某个任务完成。每一条get语句后需要一条task_done。
 9 
10 
11 import queue
12 q = queue.Queue(5)
13 q.put(10)
14 q.put(20)
15 print(q.get())
16 q.task_done()
17 print(q.get())
18 q.task_done()
19 
20 q.join()
21 
22 print("ending!")

 

 

 

其他常用方法:

此包中的常用方法(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 time,random
 2 import queue,threading
 3 
 4 q = queue.Queue()
 5 
 6 def Producer(name):
 7   count = 0
 8   while count <10:
 9     print("making........")
10     time.sleep(random.randrange(3))
11     q.put(count)
12     print('Producer %s has produced %s baozi..' %(name, count))
13     count +=1
14     #q.task_done()
15     #q.join()
16     print("ok......")
17 def Consumer(name):
18   count = 0
19   while count <10:
20     time.sleep(random.randrange(4))
21     if not q.empty():
22         data = q.get()
23         #q.task_done()
24         #q.join()
25         print(data)
26         print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data))
27     else:
28         print("-----no baozi anymore----")
29     count +=1
30 
31 p1 = threading.Thread(target=Producer, args=('A',))
32 c1 = threading.Thread(target=Consumer, args=('B',))
33 # c2 = threading.Thread(target=Consumer, args=('C',))
34 # c3 = threading.Thread(target=Consumer, args=('D',))
35 p1.start()
36 c1.start()
37 # c2.start()
38 # c3.start()

 

 

协程函数

1.由于单线程,不能再切换
2.不再有任何锁的改变
(加锁是为了保护数据)
 1 import time
 2 
 3 """
 4 传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。
 5 如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高。
 6 """
 7 # 注意到consumer函数是一个generator(生成器):
 8 # 任何包含yield关键字的函数都会自动成为生成器(generator)对象
 9 
10 def consumer():
11     r = ''
12     while True:
13         # 3、consumer通过yield拿到消息,处理,又通过yield把结果传回;
14         #    yield指令具有return关键字的作用。然后函数的堆栈会自动冻结(freeze)在这一行。
15         #    当函数调用者的下一次利用next()或generator.send()或for-in来再次调用该函数时,
16         #    就会从yield代码的下一行开始,继续执行,再返回下一次迭代结果。通过这种方式,迭代器可以实现无限序列和惰性求值。
17         n = yield r
18         if not n:
19             return
20         print('[CONSUMER] ←← Consuming %s...' % n)
21         time.sleep(1)
22         r = '200 OK'
23 def produce(c):
24     # 1、首先调用c.next()启动生成器
25     next(c)
26     n = 0
27     while n < 5:
28         n = n + 1
29         print('[PRODUCER] →→ Producing %s...' % n)
30         # 2、然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
31         cr = c.send(n)
32         # 4、produce拿到consumer处理的结果,继续生产下一条消息;
33         print('[PRODUCER] Consumer return: %s' % cr)
34     # 5、produce决定不生产了,通过c.close()关闭consumer,整个过程结束。
35     c.close()
36 if __name__=='__main__':
37     # 6、整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。
38     c = consumer()
39     produce(c)
40     
41     
42 '''
43 result:
44 
45 [PRODUCER] →→ Producing 1...
46 [CONSUMER] ←← Consuming 1...
47 [PRODUCER] Consumer return: 200 OK
48 [PRODUCER] →→ Producing 2...
49 [CONSUMER] ←← Consuming 2...
50 [PRODUCER] Consumer return: 200 OK
51 [PRODUCER] →→ Producing 3...
52 [CONSUMER] ←← Consuming 3...
53 [PRODUCER] Consumer return: 200 OK
54 [PRODUCER] →→ Producing 4...
55 [CONSUMER] ←← Consuming 4...
56 [PRODUCER] Consumer return: 200 OK
57 [PRODUCER] →→ Producing 5...
58 [CONSUMER] ←← Consuming 5...
59 [PRODUCER] Consumer return: 200 OK
60 '''

 

grennlet

greenlet机制的主要思想是:生成器函数或者协程函数中的yield语句挂起函数的执行,直到稍后使用next()或send()操作进行恢复为止。可以使用一个调度器循环在一组生成器函数之间协作多个任务。greentlet是python中实现我们所谓的"Coroutine(协程)"的一个基础库.

 

import gevent
import time

def foo():
    print("running in foo")
    gevent.sleep(2)
    print("switch to foo again")

def bar():
    print("switch to bar")
    gevent.sleep(5)
    print("switch to bar again")

start=time.time()

gevent.joinall(
    [gevent.spawn(foo),
    gevent.spawn(bar)]
)

print(time.time()-start)

  

 

gevent

Python通过yield提供了对协程的基本支持,但是不完全。而第三方的gevent为Python提供了比较完善的协程支持。

gevent是第三方库,通过greenlet实现协程,其基本思想是:

当一个greenlet遇到IO操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO。

由于切换是在IO操作时自动完成,所以gevent需要修改Python自带的一些标准库,这一过程在启动时通过monkey patch完成:

 

from gevent import monkey
monkey.patch_all()
import gevent
from urllib import request
import time

def f(url):
    print('GET: %s' % url)
    resp = request.urlopen(url)
    data = resp.read()
    print('%d bytes received from %s.' % (len(data), url))

start=time.time()

gevent.joinall([
        gevent.spawn(f, 'https://itk.org/'),
        gevent.spawn(f, 'https://www.github.com/'),
        gevent.spawn(f, 'https://zhihu.com/'),
])

# f('https://itk.org/')
# f('https://www.github.com/')
# f('https://zhihu.com/')

print(time.time()-start)

  

 

posted @ 2017-05-08 16:41  samyoung  阅读(188)  评论(0编辑  收藏  举报