资讯专栏INFORMATION COLUMN

Python系统编程之线程

XboxYan / 2027人阅读

摘要:可以将它们认为是在一个主进程或主线程中并行运行的一些迷你进程。因此与进程相比,线程之间的信息共享和通信更加容易。当上锁的线程执行完毕进行解锁,堵塞的线程就争夺到上锁权而进行代码块的运行。

threading模块 线程简述

线程(轻量级进程)与进程类似,不过它们是在同一个进程下执行的,并共享相同的上下文。可以将它们认为是在一个主进程或"主线程"中并行运行的一些"迷你进程"。

线程包括开始、执行顺序和结束三部分。它有一个指令指针,用于记录运行的上下文。它其他线程运行时,它可以被抢占(中断)和临时挂起(睡眠/加锁)---这种做法叫做让步(yielding)。

多线程的创建

使用Thread类,可以有很多方法来创建线程,其中常用的有:

创建Thread的示例,传给它一个函数;

派生Thread的子类,重新run方法,并创建子类的实例。

示例1:创建Thread的实例,传给它一个函数

from threading import Thread
import time

def test():
    print("---hello-world---")
    time.sleep(1)


for i in range(5):
    #创建线程,线程执行的任务是target指定的函数,如果函数需要传入参数,则可以指定args=(),或者kwargs={}
    t = Thread(target=test)
    t.start()

运行结果:

---hello-world---
---hello-world---
---hello-world---
---hello-world---
---hello-world---

示例2:使用Thread子类创建线程

import threading
import time

# 自定义类继承threading类
class myThread(threading.Thread):
    # 重新run方法
    def run(self):
        for i in range(3):
            time.sleep(1)
            msg = "I"m " + self.name+" @ "+str(i)
            print(msg)

if __name__ == "__main__":
    # 创建线程
    t = myThread()
    t.start()

运行结果:

I"m Thread-1 @ 0
I"m Thread-1 @ 1
I"m Thread-1 @ 2

python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。而创建自己的线程实例后,通过Thread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,就会调用run方法执行线程。

多线程共享全局变量

在一个进程中,多个线程之间是共享全局变量的,即一个线程修改了全局变量,另外一个线程在此之后获取的这个全局变量是被修改后的。比如下面例子:

from threading import Thread
import time

num = 100

def thread1():
    global num
    for i in range(3):
        num += 1
    print("I"am Thread1 ."  +  " my num is "+str(num))

def thread2():
    print("I"am Thread2. " +" my num is "+ str(num))

t1 = Thread(target=thread1)
t1.start()

# 让程序睡眠1秒钟,确保线程1执行完毕。
time.sleep(1)
t2 = Thread(target=thread2)
t2.start()

运行结果:

I"am Thread1. my num is 103
I"am Thread2.  my num is 103

线程关于全局变量注意点
在一个进程内的所有线程共享全局变量,能够在不适用其他方式的前提下完成多线程之间的数据共享(这点要比多进程要好)

一个进程中的各个线程与主线程共享同一片数据空间。因此与进程相比,线程之间的信息共享和通信更加容易。在一个程序中,线程的执行是:每个线程运行一小会,然后让步给其他线程(再次排队等待更多的CPU时间)。在整个进程的执行过程中,每个线程执行它自己特定的任务,在必要时和其他线程进行通信。

当然,这种共享是有风险的。如果两个或多个线程访问同一片数据,由于数据的访问顺序不同可能导致结果不一致。这种情况叫竞态条件。辛运的是,大多数线程库都有一些同步源语,以允许线程管理器控制执行和访问。

同步和互斥锁 同步

一般在多线程代码中,总有一些函数或者代码块不希望被多个线程同时执行,如果有两个线程运行的顺序发生变化,就有可能造成代码的执行轨迹或行为不同,产生不一样的数据。这时候就需要使用同步了。

同步可以理解为协同步调,按预定的先后次序进行运行,比如你先说,我再讲。

示例1:多个线程对全局变量修改的bug

from threading import Thread
import time

num = 0    
def work1():
    global num
    for i in range(1000000):
        num += 1
    print("-work1-num:%d"%num)    
def work2():
    global num
    for i in range(1000000):
        num += 1
    print("-work2-num:%d"%num)

t1 = Thread(target=work1)
t1.start()   
#time.sleep(3)
t2 = Thread(target=work2)
t2.start()

运行结果:

-work1-num:1105962
-work2-num:1150358

这个程序是两个线程同时对全局变量num进行相加操作,但是因为多线程中线程的执行顺序是不同的,因此出现最后相加结果不是2000000的结果。

示例2:避免全局变量被修改的方法
避免上面的情况可以有很多种方法,第一种是将上面time.sleep(3)的注释去掉,就是在3秒内让线程1执行,3s内执行完毕再执行线程2对num变量进行自增。(不过这种方法跟单线程没区别,也就没有意义去创建多线程了...)

第二种就是使用轮询,代码示例如下:

from threading import Thread
import time

num = 0
item = 1

def work1():
    global num
    global item
    if item == 1:
        for i in range(1000000):
            num += 1
        item = 0
    print("-work1-num:%d"%num)

def work2():
    global num
    while True:# 轮询,一直查看条件是否满足,线程2一直在执行...
        if item != 1:
            for i in range(1000000):
                num += 1
            break
    print("-work2-num:%d"%num)

t1 = Thread(target=work1)
t1.start()
#time.sleep(3)
t2 = Thread(target=work2)
t2.start()

运行结果:

-work1-num:1000000
-work2-num:200000

这次结果就正确的了,不过这种方法效率也不高。第三种方法就是锁了。

互斥锁

当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。

互斥锁为资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
threading模块定义了Lock类,可以很方便地进行锁定。

#创建锁
mutex = threading.Lock()
#锁定
mutex.acquire([blocking])
#释放
mutex.release()

其中,锁定方法acquire可以有一个blocking参数。

如果设定blocking为True,则当前线程会堵塞,直到获取到这个锁为止(如果没有指定,那么默认为True)

如果设定blocking为False,则当前线程不会堵

示例:

from threading import Thread,Lock
import time

num = 0

def work1():
    global num
    # 上锁
    mutex.acquire()   
    for i in range(1000000):
        num += 1
    # 解锁
    mutex.release()
    print("-work1-num:%d"%num)

def work2():
    global num
    mutex.acquire()
    for i in range(1000000):
        num += 1
    mutex.release()
    print("-work2-num:%d"%num)

# 创建一把锁,这个锁默认是没有上锁的
mutex = Lock()
t1 = Thread(target=work1)
t1.start()
#time.sleep(3)
t2 = Thread(target=work2)
t2.start()

运行结果:

-work1-num:1000000
-work2-num:2000000

代码中定义了一把锁mutex,线程t1和线程t2都互相竞争着这把锁,谁先上锁,另一方就上不了锁而堵塞。当上锁的线程执行完毕进行解锁,堵塞的线程就争夺到上锁权而进行代码块的运行。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/42956.html

相关文章

  • Python

    摘要:最近看前端都展开了几场而我大知乎最热语言还没有相关。有关书籍的介绍,大部分截取自是官方介绍。但从开始,标准库为我们提供了模块,它提供了和两个类,实现了对和的进一步抽象,对编写线程池进程池提供了直接的支持。 《流畅的python》阅读笔记 《流畅的python》是一本适合python进阶的书, 里面介绍的基本都是高级的python用法. 对于初学python的人来说, 基础大概也就够用了...

    dailybird 评论0 收藏0
  • python初学——网络编程FTP服务器支持多并发版本

    摘要:扩展支持多用户并发访问与线程池。项目请见初学网络编程之服务器。不允许超过磁盘配额。该文件是一个使用模块编写的线程池类。这一步就做到了线程池的作用。 对MYFTP项目进行升级。扩展支持多用户并发访问与线程池。MYFTP项目请见python初学——网络编程之FTP服务器。 扩展需求 1.在之前开发的FTP基础上,开发支持多并发的功能2.不能使用SocketServer模块,必须自己实现多线...

    oysun 评论0 收藏0
  • Python - 收藏集 - 掘金

    摘要:首发于我的博客线程池进程池网络编程之同步异步阻塞非阻塞后端掘金本文为作者原创,转载请先与作者联系。在了解的数据结构时,容器可迭代对象迭代器使用进行并发编程篇二掘金我们今天继续深入学习。 Python 算法实战系列之栈 - 后端 - 掘金原文出处: 安生    栈(stack)又称之为堆栈是一个特殊的有序表,其插入和删除操作都在栈顶进行操作,并且按照先进后出,后进先出的规则进行运作。 如...

    546669204 评论0 收藏0
  • Python基础使用期物处理并发

    摘要:本文重点掌握异步编程的相关概念了解期物的概念意义和使用方法了解中的阻塞型函数释放的特点。一异步编程相关概念阻塞程序未得到所需计算资源时被挂起的状态。 导语:本文章记录了本人在学习Python基础之控制流程篇的重点知识及个人心得,打算入门Python的朋友们可以来一起学习并交流。 本文重点: 1、掌握异步编程的相关概念;2、了解期物future的概念、意义和使用方法;3、了解Python...

    asoren 评论0 收藏0

发表评论

0条评论

XboxYan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<