18、Python之多线程

一、进程与线程概念

     首先说一下进程和程序的区别:程序是静态的,是存在磁盘上的,而进程是在执行中的程序,是在内存中的。起初没有线程的概率,只有进程,一个进程它有独立的资源,这就好比我们把一个班级看做一个进程,黑板,桌椅都是这个班级进程的资源,别的进程(班级)无法享用。对于外界(CPU等)而言班级是一个进程,而实际上在班级中还有很多学生,他们是一个个独立的个体,共享着班级的资源,但由于外界并不知道,一旦班级这个进程被阻塞了,班级内部的学生都无法进行活动了,这显然不适合计算机的高效,因而产生了线程(学生)被外界所认可(cpu)。由此我们可以得出进程和线程的结论为:

    1、进程是作为最小资源分配单位

    2、线程是独立运行的最小单位(被cpu调用)

    3、一个进程下有多个线程,且他们共享进程的资源

    4、一个进程至少有一个线程

   GIL(Global Interpreter Lock):全局解释器锁

   可能有些人在学python之前就听说过,python有一个缺点就是无法执行多线程,这正是由于GIL的原因。但是,需要明确的一点是GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念。就好比C++是一套语言(语法)标准,但是可以用不同的编译器来编译成可执行代码。有名的编译器例如GCC,INTEL C++,Visual C++等。Python也一样,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。所以在很多人的概念里CPython就是Python,也就想当然的把GIL归结为Python语言的缺陷。所以这里要先明确一点:GIL并不是Python的特性,Python完全可以不依赖于GIL。

二、Python中线程调用的2种方式

    方式一、直接调用

1 import threading
2 def run(n):
3     print(n)
4 
5 t1 = threading.Thread(target=run,args=("test1",))
6 t2 = threading.Thread(target=run,args=("test2",))
7 t1.start()
8 t2.start()
View Code

上面程序中一共有3个线程,t1,t2以及运行t1,t2的主线程,我们可以使用语句threading.activeCount()打印出当前活跃线程的数量。

  方式二、继承式调用

 1 import threading
 2 
 3 class MyThread(threading.Thread):
 4     def __init__(self,name):
 5         super(MyThread,self).__init__()
 6         self.__name = name
 7     def run(self):
 8         print(self.__name)
 9 
10 t1 = MyThread("test1")
11 t2 = MyThread("test2")
12 t1.start()
13 t2.start()
View Code

三、多线程中的注意点

   1、多线程中,只有一个主线程,主线程可创建多个子线程,子线程一旦被创建后,就与主线程没有半毛钱的关系。因而我们将上面的代码稍微改造一下。

 1 import threading,time
 2 
 3 class MyThread(threading.Thread):
 4     def __init__(self,name):
 5         super(MyThread,self).__init__()
 6         self.__name = name
 7     def run(self):
 8         time.sleep(1)
 9         print("线程%s运行完毕" % self.__name)
10 
11 t1 = MyThread("test1")
12 t2 = MyThread("test2")
13 t1.start()
14 t2.start()
15 print("主线程运行完毕")
View Code

运行结果为:

主线程运行完毕
线程test1运行完毕
线程test2运行完毕

由此可见,一旦主线程创建了子线程之后,将继续运行自己的代码,而子线程独立运行,但是有时候我们期望等待某个线程的执行结束,再运行主线程,这时候需要使用方法join()

上述代码要想主线程在2个子线程执行完之后再执行,代码改造如下:

 1 import threading,time
 2 
 3 class MyThread(threading.Thread):
 4     def __init__(self,name):
 5         super(MyThread,self).__init__()
 6         self.__name = name
 7     def run(self):
 8         time.sleep(1)
 9         print("线程%s运行完毕" % self.__name)
10 
11 t1 = MyThread("test1")
12 t2 = MyThread("test2")
13 t1.start()
14 t2.start()
15 t1.join()
16 t2.join()
17 print("主线程运行完毕")
View Code

    2、当主线程创建一个守护线程的时,一旦主线程运行结束,守护线程立即结束。在创建线程时使用setDaemon(True)方法就可以将子线程设置为守护线程。

 1 import threading,time
 2 
 3 class MyThread(threading.Thread):
 4     def __init__(self,name):
 5         super(MyThread,self).__init__()
 6         self.__name = name
 7     def run(self):
 8         time.sleep(1)
 9         print("线程%s运行完毕" % self.__name)
10 
11 t1 = MyThread("test1")
12 t2 = MyThread("test2")
13 t1.setDaemon(True) #将线程t1设置为守护线程
14 t2.setDaemon(True) #将线程t2设置为守护线程
15 t1.start()
16 t2.start()
17 print("主线程运行完毕")
View Code

运行结果为:主线程运行完毕,子线程还未运行完毕,就结束了。

    3、线程锁:假设有这样一个程序:

 1 import threading,time,random
 2 
 3 class MyThread(threading.Thread):
 4     count = 0
 5     def __init__(self,name):
 6         super(MyThread,self).__init__()
 7         self.__name = name
 8     def run(self):
 9         time.sleep(random.randrange(1,10))
10         MyThread.count += 1
11         print("线程%s运行完毕" % self.__name)
12     @classmethod
13     def print_count(self):
14         print(MyThread.count)
15 
16 obj_list = []
17 for i in range(50):
18     t = MyThread(i)
19     t.start()
20     obj_list.append(t)
21 for obj in obj_list:
22     obj.join()
23 MyThread.print_count()#打印总共的线程数
24 print("主线程运行完毕")
View Code

50个线程一旦被创建后,都会去修改count的值,如果不加锁的话,就会导致count最终的值是错误的(据说python三种做了处理,但是没有官方声明)。为了使count的值最终正确,我们需要给这段代码加一个锁。假设的三步骤:1、申请一把锁:lock = threading.Lock()  2、加锁 lock.acquire() 3、还锁  lock.release()。

    所以正确的代码应该是:

 1 import threading,time,random
 2 
 3 lock = threading.Lock() #申请锁
 4 class MyThread(threading.Thread):
 5     count = 0
 6     def __init__(self,name):
 7         super(MyThread,self).__init__()
 8         self.__name = name
 9     def run(self):
10         time.sleep(random.randrange(1,10))
11         lock.acquire() #加锁
12         MyThread.count += 1
13         lock.release() #释放锁
14         print("线程%s运行完毕" % self.__name)
15     @classmethod
16     def print_count(self):
17         print(MyThread.count)
18 
19 obj_list = []
20 for i in range(50):
21     t = MyThread(i)
22     t.start()
23     obj_list.append(t)
24 for obj in obj_list:
25     obj.join()
26 MyThread.print_count()#打印总共的线程数
27 print("主线程运行完毕")
View Code

四、信号量

    有时候我们系统共享的资源可能有多个,这时候需要用到信号量,信号量的使用代码示例如下:

 1 import threading,time
 2 
 3 semaphore = threading.BoundedSemaphore(5)
 4 def run(n):
 5     semaphore.acquire()
 6     time.sleep(2)
 7     print(n+1)
 8     semaphore.release()
 9 
10 for i in range(23):
11     t = threading.Thread(target=run,args=(i,))
12     t.start()
13 while threading.active_count()!=1:
14     pass
15 else:
16     print("over")
View Code

五、递归锁

    我们申请的每一把锁都应该记住其对应的对象地址,否则当有多把锁时,就会容易导致程序死锁。示例代码如下:

 1 import threading
 2 n = 0
 3 lock = {}
 4 lock["1"] = threading.Lock()
 5 lock["2"] = threading.Lock()
 6 lock["3"] = threading.Lock()
 7 #应该记清楚每一把锁
 8 def run1():
 9     global n
10     lock["1"].acquire()
11     print("run1")
12     n = n + 1
13     lock["1"].release()
14 
15 def run2():
16     global n
17     lock["2"].acquire()
18     print("run2")
19     n = n + 1
20     lock["2"].release()
21 
22 def run3():
23     global n
24     lock["3"].acquire()
25     run1()
26     run2()
27     lock["3"].release()
28 
29 t = threading.Thread(target=run3)
30 t.start()
31 while threading.active_count()!=1:
32     print(threading.active_count())
33 else:
34     print("over")
View Code

六、事件

    事件其本质就是设置一个标志位,当线程在运行的时候,判断这个标志位,如果标志位未被设置就阻塞。下面是红绿灯的一个代码示例:

 1 import threading,time,random
 2 
 3 event = threading.Event()#获取一个event对象
 4 
 5 def lighter():
 6     count = 0
 7     event.set() #设置标志位
 8     while True:
 9         if count >=0 and count<10:
10             event.clear() #清除标志位
11             print("33[41;1mred light is on...33[0m")
12         elif count>=10 and count<20:
13             event.set()
14             print("33[42;1mgreen light is on...33[0m")
15         else:
16             count = 0
17         time.sleep(1)
18         count = count + 1
19 
20 t = threading.Thread(target=lighter)
21 t.start()
22 
23 def car(n):
24     while True:
25         if event.is_set():#判断标志位是否被设置
26             print("the car is passing the accross")
27             time.sleep(random.randrange(1,10))
28             print("the %s car has accrosee the across..." % n)
29             break
30         else:
31             print("the %s car is wait..." % n)
32             event.wait()
33 
34 for i in range(20):
35     t = threading.Thread(target=car,args=(i,))
36     t.start()
View Code
原文地址:https://www.cnblogs.com/win0211/p/8549643.html