资讯专栏INFORMATION COLUMN

# Python 多线程和锁

cpupro / 3154人阅读

摘要:多线程和锁作者博客进程和线程进程是执行中的计算机程序。线程包括开始执行顺序和结束三部分。的多进程相关模块模块是高级别的多线程模块。线程锁当多线程争夺锁时,允许第一个获得锁的线程进入临街区,并执行代码。

Python 多线程和锁

作者博客:http://zzir.cn/

进程和线程

进程是执行中的计算机程序。每个进程都拥有自己的地址空间、内存、数据栈及其它的辅助数据。操作系统管理着所有的进程,并为这些进程合理分配时间。进程可以通过派生新的进程来执行其它任务,不过每个进程都拥有自己的内存和数据栈等,进程之间的数据交换采用 进程间通信(IPC) 方式。

线程在进程之下执行,一个进程下可以运行多个线程,它们之间共享相同上下文。线程包括开始、执行顺序和结束三部分。它有一个指针,用于记录当前运行的上下文。当其它线程执行时,它可以被抢占(中断)和临时挂起(也称睡眠) ——这种做法叫做 让步(yielding)

一个进程中的各个线程与主进程共享同一片数据空间,与独立进程相比,线程之间信息共享和通信更加容易。线程一般以并发执行,正是由于这种并发和数据共享机制,使多任务间的协作成为可能。当然,这种共享也并不是没有风险的,如果多个线程访问同一数据空间,由于访问顺序不同,可能导致结果不一致,这种情况通常称为竞态条件(race condition),不过大多数线程库都有同步原语,以允许线程管理器的控制执行和访问;另一个要注意的问题是,线程无法给予公平执行时间,CPU 时间分配会倾向那些阻塞更少的函数。

全局解释器锁(GIL)

Python 代码执行由 Python 虚拟机 (又名解释器主循环) 进行控制。Python 在设计时是这样考虑的,在主循环中同时只能有一个控制线程在执行。对 Python 虚拟机的访问由 全局解释器(GIL) 控制,这个锁用于,当有多个线程时保证同一时刻只能有一个线程在运行。

由于 Python 的 GIL 的限制,多线程更适合 I/O 密集型应用( I/O 释放了 GIL,可以允许更多的并发),对于计算密集型应用,为了实现更好的并行性,适合使用多进程,已便利用 CPU 的多核优势。Python 的多进程相关模块:subprocess、multiprocessing、concurrent.futures

threading 模块

threading 是 Python 高级别的多线程模块。

threading 模块的函数

active_count() 当前活动的 Thread 对象个数

current_thread() 返回当前 Thread 对象

get_ident() 返回当前线程

enumerater() 返回当前活动 Thread 对象列表

main_thread() 返回主 Thread 对象

settrace(func) 为所有线程设置一个 trace 函数

setprofile(func) 为所有线程设置一个 profile 函数

stack_size([size]) 返回新创建线程栈大小;或为后续创建的线程设定栈大小为 size

TIMEOUT_MAX Lock.acquire(), RLock.acquire(), Condition.wait() 允许的最大值

threading 可用对象列表:

Thread 表示执行线程的对象

Lock 锁原语对象

RLock 可重入锁对象,使单一进程再次获得已持有的锁(递归锁)

Condition 条件变量对象,使得一个线程等待另一个线程满足特定条件,比如改变状态或某个值

Semaphore 为线程间共享的有限资源提供一个"计数器",如果没有可用资源会被阻塞

Event 条件变量的通用版本,任意数量的线程等待某个时间的发生,在改事件发生后所有线程被激活

Timer 与 Thread 相识,不过它要在运行前等待一段时间

Barrier 创建一个"阻碍",必须达到指定数量的线程后才可以继续

Thread 类

Thread 对象的属性有:Thread.nameThread.identThread.daemon。详见(The Python Standard Library)

Thread 对象方法:
Thread.start()Thread.run()Thread.join(timeout=None)Thread.getNameThread.setNameThread.is_alive()Thread.isDaemon()Thread.setDaemon()。详见(The Python Standard Library)

使用 Thread 类,可以有很多种方法来创建线程,这里使用常见的两种:

创建 Thread 实例,传给它一个函数。

派生 Thread 子类,并创建子类的实例。

一个单线程栗子
#!/usr/bin/env python3

import threading
from random import randint
from time import sleep, ctime

def hi(n):
    sleep(n)
    print("ZzZzzz, sleep: ", n)    # 打印 Sleep 的秒数

def main():
    print("### Start at: ", ctime())

    for i in range(10):
        hi(randint(1,2))    # 调用十次,每次 Sleep 1秒或2秒

    print("### Done at: ", ctime())

if __name__ == "__main__":
    main()

运行结果:

### Start at:  Thu Sep  1 14:11:00 2016
ZzZzzz, sleep:  1
ZzZzzz, sleep:  2
ZzZzzz, sleep:  2
ZzZzzz, sleep:  2
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  2
### Done at:  Thu Sep  1 14:11:14 2016

一共是用了14秒。

多线程:创建 Thread 实例,传给它一个函数

直接上代码:

#!/usr/bin/env python3

import threading
from random import randint
from time import sleep, ctime

def hi(n):
    sleep(n)
    print("ZzZzzz, sleep: ", n)

def main():
    print("### Start at: ", ctime())
    threads = []

    for i in range(10):
        rands = randint(1,2)
        # 实例化每个 Thread 对象,把函数和参数传递进去,返回 Thread 实例
        t = threading.Thread(target=hi, args=(rands,))
        threads.append(t)     # 分配线程

    for i in range(10):
        threads[i].start()    # 开始执行多线程

    for i in range(10):
        threads[i].join()     # (自旋锁)等待线程结束或超时,然后再往下执行

    print("### Done at: ", ctime())

if __name__ == "__main__":
    main()

运行结果:

### Start at:  Thu Sep  1 14:18:00 2016
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
ZzZzzz, sleep:  2
ZzZzzz, sleep:  2
ZzZzzz, sleep:  2
ZzZzzz, sleep:  2
### Done at:  Thu Sep  1 14:18:02 2016

使用多线程,只用了2秒。

多线程:派生 Thread 子类,并创建子类的实例
#!/usr/bin/env python3

import threading
from random import randint
from time import sleep, ctime

class MyThread(threading.Thread):
 
    def __init__(self, func, args, times):
        super(MyThread, self).__init__()
        self.func = func
        self.args = args
        self.times = times
    
    def run(self):
        print("begin thread......", self.times)
        self.res = self.func(*self.args)
        print("end threads......", self.times)


def hi(n):
    sleep(n)
    print("ZzZzzz, sleep: ", n)

def main():
    print("### Start at: ", ctime())
    threads = []

    for i in range(10):
        rands = randint(1,2)
        t = MyThread(hi, (rands,), i+1)
        threads.append(t)

    for i in range(10):
        threads[i].start()

    for i in range(10):
        threads[i].join()

    print("### Done at: ", ctime())

if __name__ == "__main__":
    main()

执行结果:

### Start at:  Thu Sep  1 14:47:09 2016
begin thread...... 1
begin thread...... 2
begin thread...... 3
begin thread...... 4
begin thread...... 5
begin thread...... 6
begin thread...... 7
begin thread...... 8
begin thread...... 9
begin thread...... 10
ZzZzzz, sleep:  1
ZzZzzz, sleep:  1
end threads...... 1
end threads...... 4
ZzZzzz, sleep:  1
end threads...... 7
ZzZzzz, sleep:  1
end threads...... 3
ZzZzzz, sleep:  1
end threads...... 9
ZzZzzz, sleep:  2
end threads...... 2
ZzZzzz, sleep:  2
end threads...... 5
ZzZzzz, sleep:  2
ZzZzzz, sleep:  2
end threads...... 10
end threads...... 6
ZzZzzz, sleep:  2
end threads...... 8
### Done at:  Thu Sep  1 14:47:11 2016

这个栗子对 Thread 子类化,而不是对其实例化,使得定制线程对象更具灵活性,同时也简化线程创建的调用过程。

线程锁

当多线程争夺锁时,允许第一个获得锁的线程进入临街区,并执行代码。所有之后到达的线程将被阻塞,直到第一个线程执行结束,退出临街区,并释放锁。需要注意,那些阻塞的线程是没有顺序的。

举个栗子:

#!/usr/bin/env python3

import threading
from random import randint
from time import sleep, ctime

L = threading.Lock() # 引入锁

def hi(n):
    L.acquire()    # 加锁
    for i in [1,2]:
        print(i)
        sleep(n)
        print("ZzZzzz, sleep: ", n)
    L.release()    # 释放锁

def main():
    print("### Start at: ", ctime())
    threads = []

    for i in range(10):
        rands = randint(1,2)
        t = threading.Thread(target=hi, args=(rands,))
        threads.append(t)

    for i in range(10):
        threads[i].start()

    for i in range(10):
        threads[i].join()

    print("### Done at: ", ctime())

if __name__ == "__main__":
    main()

运行上面的代码,再将锁的代码注释掉,对比下输出。

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

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

相关文章

  • Java 8 并发:同步和锁

    摘要:可重入意味着锁被绑定到当前线程,线程可以安全地多次获取相同的锁,而不会发生死锁例如同步方法在同一对象上调用另一个同步方法。写入锁释放后,两个任务并行执行,它们不必等待对方是否完成,因为只要没有线程持有写入锁,它们就可以同时持有读取锁。 原文地址: Java 8 Concurrency Tutorial: Synchronization and Locks 为了简单起见,本教程的示例代...

    andycall 评论0 收藏0
  • Java 8 并发教程:同步和锁

    摘要:在接下来的分钟,你将会学会如何通过同步关键字,锁和信号量来同步访问共享可变变量。所以在使用乐观锁时,你需要每次在访问任何共享可变变量之后都要检查锁,来确保读锁仍然有效。 原文:Java 8 Concurrency Tutorial: Synchronization and Locks译者:飞龙 协议:CC BY-NC-SA 4.0 欢迎阅读我的Java8并发教程的第二部分。这份指南将...

    wyk1184 评论0 收藏0
  • python并发4:使用thread处理并发

    摘要:如果某线程并未使用很多操作,它会在自己的时间片内一直占用处理器和。在中使用线程在和等大多数类系统上运行时,支持多线程编程。守护线程另一个避免使用模块的原因是,它不支持守护线程。 这一篇是Python并发的第四篇,主要介绍进程和线程的定义,Python线程和全局解释器锁以及Python如何使用thread模块处理并发 引言&动机 考虑一下这个场景,我们有10000条数据需要处理,处理每条...

    joywek 评论0 收藏0
  • Python进程专题5:进程间通信

    摘要:上一篇文章进程专题进程池下一篇文章进程专题共享数据与同步模块支持的进程间通信主要有两种管道和队列。队列底层使用管道和锁,同时运行支持线程讲队列中的数据传输到底层管道中,来实习进程间通信。 上一篇文章:Python进程专题4:进程池Pool下一篇文章:Python进程专题6:共享数据与同步 multiprocessing模块支持的进程间通信主要有两种:管道和队列。一般来说,发送较少的大...

    eccozhou 评论0 收藏0
  • 不可不说的Java“锁”事

    摘要:本文旨在对锁相关源码本文中的源码来自使用场景进行举例,为读者介绍主流锁的知识点,以及不同的锁的适用场景。中,关键字和的实现类都是悲观锁。自适应意味着自旋的时间次数不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。 前言 Java提供了种类丰富的锁,每种锁因其特性的不同,在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码(本文中的源码来自JDK 8)、使用场景...

    galaxy_robot 评论0 收藏0

发表评论

0条评论

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