[Python-线程]

多任务实现方法:

  • 多进程
  • 多线程
  • 一个进程内创建多个线程

线程是操作系统直接支持的执行单元,因此,高级语言中大多内置了多线程的支持,Python的多线程是真正的Posix Thread,而不是模拟出来的多线程

Python多线程实现

Python提供两个高级库:_thread和thread,_thread是低级模块,thread是高级模块,对_thread进行了封装, 大多数情况下使用thread

两种方法使用线程:

  1. 传入一个函数
  2. 继承自threading.Thread的类

方法一: 启动一个线程的方法:把一个函数传入并创建Thread实例,然后调用start()函数开始执行:

import time, threading

# 新线程执行的代码:
def loop():
    print('thread %s is running...' % threading.current_thread().name)
    n = 0
    while n < 5:
        n = n + 1
        print('thread %s >>> %s' % (threading.current_thread().name, n))
        time.sleep(1)
    print('thread %s ended.' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
t = threading.Thread(target=loop, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

执行结果如下:

thread MainThread is running...
thread LoopThread is running...
thread LoopThread >>> 1
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
thread MainThread ended.

由于任何进程默认就会启动一个线程,我们把该线程称为主线程,主线程又可以启动新的线程,Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。主线程实例的名字叫MainThread,子线程的名字在创建时指定,我们用LoopThread命名子线程。名字仅仅在打印时用来显示,完全没有其他意义,如果不起名字Python就自动给线程命名为Thread-1,Thread-2……

方法二: 使用继承自threading.Thread的类

import threading
import time

exitFlag = 0

class myThread (threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print "Starting " + self.name
        # 获得锁,成功获得锁定后返回True
        # 可选的timeout参数不填时,将一直阻塞知道获得锁定
        # 否则超时后将返回False
        threadLock.acquire()
        print_time(self.name, self.counter, 5)
        # 释放锁
        threadLock.release()
        print "Exiting " + self.name

def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            thread.exit()
        time.sleep(delay)
        print "%s: %s" % (threadName, time.ctime(time.time()))
        counter -= 1

threadLock = threading.Lock()
threads = []
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 开启线程
thread1.start()
thread2.start()

# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)

# 等待所有线程完成
for t in threads:
    t.join()
print "Exiting Main Thread"

执行结果如下:


Paste_Image.png

几点注意的地方:

  1. 上面的程序我们使用了Lock(线程锁),对资源进行独占,锁的使用方法如下:
    1. 创建一个锁实例: **threadLock = threading.Lock()
    2. 给需要独占的资源上锁:
    threadingLock.acquire() # 获得锁
    此处添加需要独占的资源
    threadingLock.release() # 资源用完后,需要释放锁
  2. 使用了join()函数,join()函数的作用总结如下:
    1. 阻塞主进程无法执行join以后的语句,专注执行多线程,必须等待多线程执行完毕之后才能执行主线程的语句。
    2. 多线程多join的情况下,依次执行各线程的join方法,前一个结束之后,才能执行后一个。(在上面的程序中,使用join()的这一条功能)
    3. 无参数,则等待到该线程结束,才开始执行下一个线程的join。
    4. 设置参数后,则等待该线程N秒之后不管该线程是否结束,就开始执行后面的主进程。
  3. run()函数:在创建线程后会自动执行run()函数

Lock锁

多进程与多线程最大的区别在于:多进程中,同一个变量,各自有一份拷贝存在于进程中,互不影响(可以理解为:多进程中,每个进程都有一个堆栈空间存放各自的运行环境)而在多线程中,所有变量均有所有线程共享,因此,多线程最大的危险在于多个线程更改同一个变量,导致内容变乱

为了解决这个问题,python中有一个lock,可以让线程对某一个资源独享,通过

    lock = threading.Lock()实现
balance = 0
lock = threading.Lock()

def run_thread(n):
    for i in range(100000):
        # 先要获取锁:
        lock.acquire()
        try:
            # 放心地改吧:
            change_it(n)
        finally:
            # 改完了一定要释放锁:
            lock.release()

多个线程同时执行lock.acquire(),只有一个线程能够获得锁

获得锁的线程,用完了资源后,要及时释放锁,不然会导致其他线程一直被挂起,

死锁: 锁的机制可以确保资源的独占,但是也会使得某段代码只能以单线程的形式执行,不能并发,同时,不同线程可以有不同的锁,为了获得对方的锁,可能会导致死锁的情况,如:线程A锁住了记录1并等待记录2,而线程B锁住了记录2并等待记录1,这样两个线程就发生了死锁现象。

三个示例:
1. 未使用锁的示例


# 未使用锁的情况
class myThread (threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print "Starting " + self.name
        print_time(self.name, self.counter, 5)
        print "Exiting " + self.name

def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            thread.exit()
        time.sleep(delay)
        print "%s: %s" % (threadName, time.ctime(time.time()))
        counter -= 1

# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 开启线程
thread1.start()
thread2.start()
print "Exiting Main Thread"

执行结果:

Paste_Image.png

几点注意:
在上面的程序中,我们未使用锁,也没有使用join()函数

  1. 从打印结果中可以看到,在最前面的三条打印中,显示的是:Starting Thread-1、Starting Thread-2和Exiting Main Thread。说明启动thread1和thread2之后,它们均进入到了sleep状态,这时继续往下执行了主线程,
    2.从打印中可以看到Thread-1和Thread-2是交替执行的,并不是执行完Thread-1的全部代码后再执行Thread-2

2. 使用锁,但不使用join()的示例

class myThread (threading.Thread):
    def __init__(self, threadID, name, counter):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.name = name
        self.counter = counter

    def run(self):
        print "Starting " + self.name
        # 获得锁,成功获得锁定后返回True
        # 可选的timeout参数不填时,将一直阻塞知道获得锁定
        # 否则超时后将返回False
        threadLock.acquire()
        print_time(self.name, self.counter, 5)
        # 释放锁
        threadLock.release()
        print "Exiting " + self.name

def print_time(threadName, delay, counter):
    while counter:
        if exitFlag:
            thread.exit()
        time.sleep(delay)
        print "%s: %s" % (threadName, time.ctime(time.time()))
        counter -= 1

threadLock = threading.Lock()
threads = []
# 创建新线程
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)

# 开启线程
thread1.start()
thread2.start()

# 添加线程到线程列表
threads.append(thread1)
threads.append(thread2)

print "Exiting Main Thread"

执行结果:

Paste_Image.png

几点注意

  1. 使用锁之后,只有等thread1执行完成并释放锁之后,才执行thread2的代码
  2. 主线程并没有等到thread1和thread2全部执行完成后才结束,而是在运行过程中逮到机会就直接运行

3. 第三个实例使用了锁和join()函数
该实例在文章上面的方法二: 使用继承自threading.Thread的类创建线程

小结

python解释器在执行代码时,有一个GIL锁:Global Interpreter Lock,任何python代码执行前必须获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行,GIL锁,实际给所有线程的执行代码都上了锁,因此,多线程在python中只能交替执行

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,837评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,551评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,417评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,448评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,524评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,554评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,569评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,316评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,766评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,077评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,240评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,912评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,560评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,176评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,425评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,114评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,114评论 2 352

推荐阅读更多精彩内容

  • 线程状态新建,就绪,运行,阻塞,死亡。 线程同步多线程可以同时运行多个任务,线程需要共享数据的时候,可能出现数据不...
    KevinCool阅读 798评论 0 0
  • 1.线程 Python中使用线程有两种方式:函数或者用类来包装线程对象。 1.函数式:调用thread模块中的st...
    一只写程序的猿阅读 997评论 0 1
  • 多任务可以由多进程完成,也可以由一个进程内的多线程完成。我们前面提到了进程是由若干线程组成的,一个进程至少有一个线...
    壁花烧年阅读 813评论 0 0
  • 引言&动机 考虑一下这个场景,我们有10000条数据需要处理,处理每条数据需要花费1秒,但读取数据只需要0.1秒,...
    chen_000阅读 508评论 0 0
  • 从什么都没有的地方,到什么都没有的地方。 走马说,她想回家了。她说,帝都这座城,她再也不想来了。 我认识她的时候,...
    林斯野阅读 1,644评论 0 0