我的憨憨女友都能看懂学会的python多线程

网友投稿 656 2022-05-29

我和我的女朋友因为python而相识,同时也是因为python我才能把憨憨追到手。最近我和我女朋友在做一个项目,我负责语音识别和TTS,她负责QT界面设计。终于在上一个周我们都完成了各自预期的功能。到了两个代码整合的阶段,却发现了一个难题:怎么样才能实现语音和界面同时工作,同时怎么样才能保证通过语音来打开相关的界面,以及在视频通话时语音不工作,这些问题让我俩抓狂。看看我女朋友的头发最近掉的厉害,作为一个男人我必须扛起责任!于是我拦下这活,并且给我女朋友说道:等我学会了python多线程我讲给你听!

文章目录

线程和进程

多线程与多进程

我的憨憨女友都能看懂学会的python多线程

python多线程的实现

threading

自定义线程

守护线程

主线程等待子线程运行结束

多线程共享全局变量

互斥锁

递归锁

信号量(BoundedSemaphore类)

事件(Event类)

Qthread

线程和进程

计算机的核心是CPU,它承担了所有的计算任务,就像是一座工厂在时刻运行

如果工厂的资源有限,一次只能供一个车间来使用,也就是说当一个车间开工时其它车间不能工作,也就是一个CPU一次只能执行一个任务。

进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。

当然一个车间还有很多工人,他们互相协同完成一个工作

而线程就好比工厂的工人,一个进程可以包含多个线程

线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。

多线程与多进程

通俗易懂的理解就是:

多进程:允许多个任务同时进行 多线程:允许单个任务分成不同的部分运行

1

2

python多线程的实现

Python3 通过两个标准库 thread (python2中是thread模块)和 threading 提供对线程的支持。

thread 提供了低级别的、原始的线程以及一个简单的锁,它相比于 threading 模块的功能还是比较有限的。

threading

import threading #导入threading库 import time def run(n): print("task", n) time.sleep(1) #延时一秒 print('2s') time.sleep(1) print('1s') time.sleep(1) print('0s') time.sleep(1) if __name__ == '__main__': t1 = threading.Thread(target=run, args=("t1",))#创建线程1,取名为t1 t2 = threading.Thread(target=run, args=("t2",))#创建线程2,取名为t2 t1.start() #开启线程t1 t2.start() #开启线程t2

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

输出结果:

task t1 task t2 2s 2s 1s 1s 0s 0s

1

2

3

4

5

6

7

8

可以看出先开启了线程t1,在开启t2然后每隔一秒打印数据

自定义线程

通过继承threading.Thread来自定义线程类,其本质是

重构Thread类中的run方法

import threading import time class MyThread(threading.Thread): def __init__(self, n): super(MyThread, self).__init__() # 重构run函数必须要写 self.n = n def run(self): print("task", self.n) time.sleep(1) print('2s') time.sleep(1) print('1s') time.sleep(1) print('0s') time.sleep(1) if __name__ == "__main__": t1 = MyThread("t1") t2 = MyThread("t2") t1.start() t2.start()

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

输出结果:

task t1 task t2 2s 2s 1s 1s 0s 0s

1

2

3

4

5

6

7

8

守护线程

下面这个例子,使用setDaemon(True)把所有的子线程都变成了主线程的守护线程,因此当主进程结束后,子线程也会随之结束。所以当主线程结束后,整个程序就退出了。

import threading import time def run(n): print("task", n) time.sleep(1) #此时子线程停1s print('3') time.sleep(1) print('2') time.sleep(1) print('1') if __name__ == '__main__': t = threading.Thread(target=run, args=("t1",)) t.setDaemon(True) #把子进程设置为守护线程,必须在start()之前设置 t.start() print("end")

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

输出结果:

task t1 end

1

2

可以看到,t1线程并没有执行完毕,而是直接结束了。说明设置子线程为守护线程之后,主线程结束了,子线程也立即结束不再执行。

程序中不是只创建了一个线程么?怎么会有主线程和子线程呢?

其实呢程序运行时就会创建一个线程,而这个线程就是主线程

主线程等待子线程运行结束

import threading import time def run(n): print("task", n) time.sleep(1) print('3') time.sleep(1) print('2') time.sleep(1) print('1') if __name__ == '__main__': t = threading.Thread(target=run, args=("t1",)) t.setDaemon(True) #把子进程设置为守护线程,必须在start()之前设置 t.start() t.join() # 设置主线程等待子线程结束 print("end")

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

输出结果:

task t1 3 2 1 end

1

2

3

4

5

运行.join()后的程序表明等待所有线程结束以后再进行.join()之后的操作结合以上代码就是,等待t1结束以后再执行end

多线程共享全局变量

线程是进程的执行单元,进程是系统分配资源的最小单位,所以在同一个进程中的多线程是共享资源的。那么共享资源时就需要用到全局变量。

import threading import time num = 100 def work1(): global num for i in range(3): num += 1 print("in work1 num is : %d" % num) def work2(): global num print("in work2 num is : %d" % num) if __name__ == '__main__': t1 = threading.Thread(target=work1) t1.start() time.sleep(1) t2 = threading.Thread(target=work2) t2.start()

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

运行结果如下:

in work1 num is : 103 in work2 num is : 103

1

2

可以看到两者输出的结果是相同的,说明是可以共享全局变量的。

互斥锁

由于线程之间是进行随机调度,并且每个线程可能只执行n条,当多个线程同时修改同一条数据时可能会出现脏数据,因而,出现了线程锁,即同一时刻只允许一个线程执行操作。线程锁用于锁定资源,可以定义多个锁, 在下面的实例中, 当你需要独占某一资源时,任何一个锁都可以锁这个资源,就好比你用不同的锁都可以把相同的一个门锁住是一个道理。

由于线程之间是进行随机调度,如果有多个线程同时操作一个对象,如果没有很好地保护该对象,会造成程序结果的不可预期,我们也称此为“线程不安全”。

为了方式上面情况的发生,就出现了互斥锁(Lock)

import threading def work1(): global A,lock#定义A和lock为全局变量 lock.acquire()#上锁 for i in range(5): A+=1 print('work1',A) lock.release()#解锁 def work2(): global A,lock lock.acquire() for i in range(5): A+=10 print('work2',A) lock.release() if __name__=='__main__': lock=threading.Lock()#定义锁 A=0 t1=threading.Thread(target=work1) t2=threading.Thread(target=work2) t1.start() t2.start() t1.join() t2.join()

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

输出结果:

work1 1 work1 2 work1 3 work1 4 work1 5 work2 15 work2 25 work2 35 work2 45 work2 55

1

2

3

4

5

6

7

8

9

10

可以发现对两组数据是没有影响的,感兴趣的可以尝试一下不加锁会有什么情况。

递归锁

RLcok类的用法和Lock类一模一样,但它支持嵌套,在多个锁没有释放的时候一般会使用RLcok类。

import threading import time def Func(lock): global gl_num lock.acquire() gl_num += 1 time.sleep(1) print(gl_num) lock.release() if __name__ == '__main__': gl_num = 0 lock = threading.RLock() for i in range(10): t = threading.Thread(target=Func, args=(lock,)) t.start()

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

输出结果:

1 2 3 4 5 6 7 8 9 10

1

2

3

4

5

6

7

8

9

10

信号量(BoundedSemaphore类)

互斥锁同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。

实际中博主还没有用到过,所以理解不是特别透彻。

import threading import time def run(n, semaphore): semaphore.acquire() #加锁 time.sleep(1) print("run the thread:%s\n" % n) semaphore.release() #释放 if __name__ == '__main__': num = 0 semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行 for i in range(22): t = threading.Thread(target=run, args=("t-%s" % i, semaphore)) t.start() while threading.active_count() != 1: pass # print threading.active_count() else: print('-----all threads done-----')

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

输出结果有点长,就不贴输出结果了。

事件(Event类)

python线程的事件用于主线程控制其他线程的执行,事件是一个简单的线程同步对象,其主要提供以下几个方法:

clear 将flag设置为“False”

set 将flag设置为“True”

is_set 判断是否设置了flag

wait 会一直监听flag,如果没有检测到flag就一直处于阻塞状态

事件处理的机制:全局定义了一个“Flag”,当flag值为“False”,那么event.wait()就会阻塞,当flag值为“True”,那么event.wait()便不再阻塞

import threading import time event = threading.Event() def lighter(): count = 0 event.set() #初始值为绿灯 while True: if 5 < count <=10 : event.clear() # 红灯,清除标志位 print("1mred light is on...") elif count > 10: event.set() # 绿灯,设置标志位 count = 0 else: print("mgreen light is on...") time.sleep(1) count += 1 def car(name): while True: if event.is_set(): #判断是否设置了标志位 print("[%s] running..."%name) time.sleep(1) else: print("[%s] sees red light,waiting..."%name) event.wait() print("[%s] green light is on,start going..."%name) light = threading.Thread(target=lighter,) light.start() car = threading.Thread(target=car,args=("MINI",)) car.start()

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

这段代码模拟红绿灯,很形象。

Qthread

本以为我学完了多线程就完事了,就可以将语音和QT界面进行整合了。当我去实现的时候发现问题不是这么简单,通过语音控制打开一个特定的界面可以实现,但是为什么只要这个特定的界面关闭了,我语音的线程也就结束了。

困惑了我好久,最后终于在某社区发现了答案!原来QT自带的有Qthread,当多线程涉及到界面交互时最好用Qthread来实现。然后又查阅大量博客,看了大量代码

在使用继承QThread的run方法之前需要了解一条规则:

QThread只有run函数是在新线程里的,其他所有函数都在QThread生成的线程里

QThread只有run函数是在新线程里的

QThread只有run函数是在新线程里的

QThread只有run函数是在新线程里的

那么我就在网上找到了这个计时器的例子:

#coding=utf-8 import sys from PyQt5.QtGui import * from PyQt5.QtWidgets import * from PyQt5.QtCore import * count = 0 # 工作线程 class WorkThread(QThread): # pyqtSignal是信号类 timeout = pyqtSignal() # 每隔一秒发送一个信号 end = pyqtSignal() # 计数完成后发送一个信号 def run(self): while True: # 休眠1秒 self.sleep(1) if count == 5: self.end.emit() # 发送end信号,调用和end信号关联的方法 break self.timeout.emit() # 发送timeout信号 class Counter(QWidget): def __init__(self): super(Counter, self).__init__() self.setWindowTitle("用QThread编写计数器") self.resize(600, 400) layout = QVBoxLayout() # QLCDNumber 用于模拟LED显示效果,类似于Label self.lcdNumber = QLCDNumber() layout.addWidget(self.lcdNumber) button = QPushButton("开始计数") layout.addWidget(button) self.workThread = WorkThread() self.workThread.timeout.connect(self.countTime) self.workThread.end.connect(self.end) button.clicked.connect(self.work) self.setLayout(layout) def countTime(self): global count count += 1 self.lcdNumber.display(count) def end(self): QMessageBox.information(self, '消息', '计数结束', QMessageBox.Ok) global count count =0 def work(self): self.workThread.start() if __name__ == "__main__": app = QApplication(sys.argv) main = Counter() main.show() sys.exit(app.exec_())

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

点击开始计时就会出现类似LCD的显示,计时到5秒结束后弹窗提醒。

运行结果如下:

通过这个例程让我对Qthread有了更好的理解,经管理解的不是特别透彻但是我知道怎么来改出来我想用的代码。之前提到的打开窗口线程阻塞,关闭窗口线程重启,其实这个计时器是一个很好的例子,但是关于线程阻塞.wait不好使。我的方法是定义一个全局变量mode=0(用来判断是否需要阻塞线程),如果窗口打开后那么给这个全局赋值mode=1,在run函数里对这个mode进行判断,如果mode等于1那么可以用一个循环来延时实现。

if mode: while(mode): self.sleep(1)

1

2

3

当窗口关闭以后给mode 赋值等于0通过这种方法可以实现,很多小伙伴又会问怎么判断窗口打开和关闭,其实在自己写的窗口函数最前面加mode=1和最后面mode=0就可以了不用进行判断。

在看完这篇文章后,我女朋友终于给我发来了下面的表情,对我投来羡慕的眼神

Python 任务调度 多线程

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:多线程小抄集(新编四)
下一篇:如何从零起步开发一款App
相关文章