简单明了的 Python 多线程来了

线程和进程
计算机的核心是CPU,它承担了所有的计算任务,就像是一座工厂在时刻运行。
如果工厂的资源有限,一次只能供一个车间来使用,也就是说当一个车间开工时其它车间不能工作,也就是一个CPU一次只能执行一个任务。
进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
当然一个车间还有很多工人,他们互相协同完成一个工作;而线程就好比工厂的工人,一个进程可以包含多个线程。
线程(Thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位。线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
多线程与多进程
通俗易懂的理解就是:
多进程:允许多个任务同时进行
多线程:允许单个任务分成不同的部分运行
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

输出结果:
task t1
task t2
2s
2s
1s
1s
0s
0s
可以看出先开启了线程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()

输出结果:
task t1
task t2
2s
2s
1s
1s
0s
0s
守护线程
下面这个例子,使用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")

输出结果:
task t1
end
可以看到,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")

输出结果:
task t1
3
2
1
end
运行.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()

运行结果如下:
in work1 num is : 103
in work2 num is : 103
可以看到两者输出的结果是相同的,说明是可以共享全局变量的。
互斥锁
由于线程之间是进行随机调度,并且每个线程可能只执行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()

输出结果:
work1 1
work1 2
work1 3
work1 4
work1 5
work2 15
work2 25
work2 35
work2 45
work2 55
可以发现对两组数据是没有影响的,感兴趣的可以尝试一下不加锁会有什么情况。
递归锁
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
信号量(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-----')

输出结果有点长,就不贴输出结果了。
事件(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()

这段代码模拟红绿灯,很形象。
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_())

点击开始计时就会出现类似LCD的显示,计时到5秒结束后弹窗提醒。
运行结果如下:
通过这个例程让我对Qthread有了更好的理解,经管理解的不是特别透彻但是我知道怎么来改出来我想用的代码。之前提到的打开窗口线程阻塞,关闭窗口线程重启,其实这个计时器是一个很好的例子,但是关于线程阻塞.wait不好使。我的方法是定义一个全局变量mode=0(用来判断是否需要阻塞线程),如果窗口打开后那么给这个全局赋值mode=1,在run函数里对这个mode进行判断,如果mode等于1那么可以用一个循环来延时实现。
if mode:
    while(mode):
        self.sleep(1)
当窗口关闭以后给mode 赋值等于0通过这种方法可以实现,很多小伙伴又会问怎么判断窗口打开和关闭,其实在自己写的窗口函数最前面加mode=1和最后面mode=0就可以了不用进行判断。
(0)

相关推荐

  • python 多线程知识全面解析

    Python编程学堂 4天前 非阻塞启动线程 import threadingimport timedef one_thread(name,id): print("start....&quo ...

  • python笔记10-多线程之线程同步(锁lock)

    前言 关于吃火锅的场景,小伙伴并不陌生,吃火锅的时候a同学往锅里下鱼丸,b同学同时去吃掉鱼丸,有可能会导致吃到生的鱼丸. 为了避免这种情况,在下鱼丸的过程中,先锁定操作,让吃火锅的小伙伴停一会,等鱼丸 ...

  • 编程语言Python进程和线程保姆式教学,1个台机子多只手干活的秘籍

    进程线程有多重要?刚开始学Python的时候你可能还没有感觉到,因为你写的代码从上到下执行一遍就可以了,但实际上这很初级,实际开发写项目的时候,为了充分利用电脑配置来加快程序进度,我们往往会用到多进程 ...

  • Python进程与线程知识

    Python进程与线程知识,Python开发语言现在已经是被大家非常看中的编程语言了,本篇文章给读者们分享一下Python进程与线程知识小结,本篇文章具有一定的参考借鉴价值,感兴趣的小伙伴来了解一下吧 ...

  • python笔记11-多线程之Condition(条件变量)

    前言 当小伙伴a在往火锅里面添加鱼丸,这个就是生产者行为:另外一个小伙伴b在吃掉鱼丸就是消费者行为.当火锅里面鱼丸达到一定数量加满后b才能吃,这就是一种条件判断了. 这就是本篇要讲的Condition ...

  • Python - 进程、线程与协程

    在操作系统中,每一个独立运行的程序,都占有 操作系统 分配的资源,这些程序中间互不干涉,都只负责运行自己的程序代码,这就是进程. 但是当操作系统频繁的创建销毁进程时,大量的系统资源被浪费在创建和销毁的 ...

  • python笔记7-多线程threading

    前言 1.python环境2.7 2.threading模块系统自带 一. 单线程 1.平常写的代码都是按顺序挨个执行的,就好比吃火锅和哼小曲这两个行为事件,定义成两个函数,执行的时候,是先吃火锅再哼 ...

  • python 多线程就这么简单

    多线程和多进程是什么自行google补脑 对于python 多线程的理解,我花了很长时间,搜索的大部份文章都不够通俗易懂.所以,这里力图用简单的例子,让你对多线程有个初步的认识. 单线程 在好些年前的 ...

  • 「翔博精选指标」 同花顺神庄指标幅图选股指标信号简单明了

    做价值的传播者,一路同行,一起成长 适用软件:通达信 公式说明:不包含未来函数,不加密,副图公式 指标公式描述 使用说明:1.红色走势线上穿庄线买入: 2.红色线高点钝化拐头往下或绿色散户线低点往上翘 ...

  • 简单明了的10个字,略带章草笔意,却摘得全国第三届草书最高奖

    在第三届草书作品展中,有这样的一幅作品,引起了评委们的注意,简简单单的10字对联,"破壁群龙舞,临池五凤飞",却有很强的视觉冲击力,不仅如此,还赢得了评委们的"芳心&qu ...

  • (简单明了)塑化参数设置与优化方法

    戳我进入社区:注塑和模具人的网上家园 注射量 注射量是指注塑机螺杆(或柱塞)在注射时,向模具内所注射的物料熔体量( g ).因此,注射量是由聚合物的物理性能及螺杆中料筒中的推进容积来确定的. 由此可见 ...

  • 简单明了的发动机技术运作原理,这次真的看懂了

    简单明了的发动机技术运作原理,这次真的看懂了 简单明了的发动机技术运作原理,这次真的看懂了 展开

  • 简单明了,从零开始欣赏远古文明奇门遁甲文化

    九天玄女 奇门遁甲,传自远古圣贤九天玄女 九天玄女体恤民难,遂授予轩辕黄氏奇门遁甲 轩辕黄氏 轩辕黄帝战蚩尤,涿鹿经年苦未休, 偶梦天神授符诀,登坛致祭谨虔修. 注意:天神既指九天玄女 第一略:奇门遁 ...

  • 这位老师总结的钢笔字练习秘诀,简单明了,堪称硬笔书法界的宝典!

    111 转自今日头条:80后技术员,版权归原作者. 钢笔字速成秘诀----逸风(原作者) 书法练习需要持之以恒,不能三心二意,更没有捷径可寻,但有一定的巧门,掌握了这些巧门可以使你的练习效果卓然不同. ...

  • 老子《四字圣典》:先贤智慧,简单明了,人生至少读一篇

    引子 道本无名.朴而自然:无形无状.无人能臣. 以道佐霸.不以兵胜,兵出兵入.必陷因果, 善胜霸主.敢动知止.得果而已.不欲逞强. 知始知止.永无停止,道于天下.如川归海. 众人熙熙.如享大牢.如春登 ...

  • 简单明了的抄底八法

    对于实战中看K线形态来测定底部,我认为普通投资者可主要掌握8个特征,甚至可以像数学公式那样背下来.因为回顾市场多年的历史走势,甚至世界各国不同市场的底部区域,几乎都能证明以下这些规律是中期.长期底部所 ...