从零开始学Python(八):Python多线程和队列

很久没有更新博文啦,在家过春节已经变懒了-_-,不过答应大家更完这个python的入门系列,偶还是会继续努力的!另外祝愿大家新年快乐,事事顺心!

线程的概念

我们学习的很多编程语言,比如java,oc等,都会有线程这个概念.线程的用途非常的广泛,给我们开发中带来了很多的便利.主要用于一些串行或者并行的逻辑处理,比如点击某个按钮的时候,我们可以通过进度条来控制线程的运行时间,以便于更好的用于用户的交互.

每个独立的线程都包含一个程序的运行入口,顺序的执行序列和一个程序运行的出口.线程必须在程序中存在,而不能独立于程序运行!

每个线程都有他自己的一组cpu储存器,称为线程的上下文,该上下文反应了线程上次运行的cpu寄存器的状态.指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文运行,这些地址都用于标志拥有线程的进程地址空间中的内存.

Python线程

在Python中,主要提供了thread和threading两个线程模块,thread模块提供了最基础的,最低级的线程函数,和一个简单的锁.threading模块是thread模块的封装进阶,提供了多样的线程属性和方法.下面我们会对该两个模块逐个解析.

thread模块(不推荐使用)

thread模块常用的函数方法:

函数名 描述
start_new_thread(function, args, kwargs=None) 产生一个新线程,function为线程要运行的函数名,args是函数参数(tuple元组类型),kwargs为可选参数
allocate_lock() 分配一个locktype类型的线程锁对象
exit() 线程退出
_count() 返回线程数量,注意不包含主线程哦,所以在主线程运行该方法返回的是0
locked locktype 锁,返回true为已锁
release() 释放locktype对象锁
acquire() 锁定

下面我们来举个例子:

import thread,time


def loop1():
    print '线程个数-' + str(thread._count())
    i=0
    try:
        while i < 100:
            print i
            time.sleep(1)
            i = i + 1
    except Exception as e:
        print e


thread.start_new_thread(loop1,())

运行上面代码,你会发现loop1方法中的循环打印并没有被调用,而是直接返回了一个异常:

Unhandled exception in thread started by 
sys.excepthook is missing
lost sys.stderr

这时你可能会一遍又一遍的检查代码,以为是代码错了(没错,那个人就是我),其实我们代码本身是没有错误的,是早期python的thread模块一个缺陷(这个缺陷也是导致这个模块被官方不推荐使用的主要原因):当我们在主线程中使用start_new_thread创建新的线程的时候,主线程无法得知线程何时结束,他不知道要等待多久,导致主线程已经执行完了,子线程却还未完成,于是系统抛出了这个异常.

解决这个异常的方法有两种:

1.让主线程休眠足够长的时间来等待子线程返回结果:

import thread,time


def loop1():
    print '线程个数-' + str(thread._count())
    i=0
    try:
        while i < 100:
            print i
            time.sleep(1)
            i = i + 1
    except Exception as e:
        print e


thread.start_new_thread(loop1,())
time.sleep(1000)   #让主线程休眠1000秒,足够子线程完成

2.给线程加锁(早期python线程使用一般处理)

import thread,time


def loop1(lock):
    print '线程个数-' + str(thread._count())
    i=0
    try:
        while i < 100:
            print i
            time.sleep(1)
            i = i + 1
    except Exception as e:
        lock.release()
        print e

    lock.release()    #执行完毕,释放锁


lock=thread.allocate_lock()   #获取locktype对象
lock.acquire()   #锁定

thread.start_new_thread(loop1,(lock,))
while lock.locked():    #等待线程锁释放
    pass

以上就是thread模块的常用线程用法,我们可以看出,thread模块提供的线程操作是极其有限的,使用起来非常的不灵活,下面我们介绍他的同胞模块threading.

threading模块(推荐使用)

threading模块是thread的完善,有一套成熟的线程操作方法,基本能完成我们所需的所有线程操作

threading常用方法:

  • threading.currentThread(): 返回当前的线程变量。
  • threading.enumerate(): 返回一个包含正在运行的线程的list。 正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
  • threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
  • run(): 用以表示线程活动的方法。
  • start():启动线程活动。
  • join([time]): 等待至线程中止。 这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。
  • isAlive(): 返回线程是否活动的。
  • getName(): 返回线程名。
  • setName(): 设置线程名。

threading模块创建线程有两种方式:

1.直接通过初始化thread对象创建:

#coding=utf-8
import threading,time

def test():
    t = threading.currentThread()  # 获取当前子线程对象
    print t.getName()  # 打印当前子线程名字

    i=0
    while i<10:
        print i
        time.sleep(1)
        i=i+1




m=threading.Thread(target=test,args=(),name='循环子线程')   #初始化一个子线程对象,target是执行的目标函数,args是目标函数的参数,name是子线程的名字
m.start()
t=threading.currentThread()   #获取当前线程对象,这里其实是主线程
print t.getName()   #打印当前线程名字,其实是主线程名字

可以看到打印结果:

循环子线程
MainThread
0
1
2
3
4
5
6
7
8

2.通过基础thread类来创建

import threading,time
class myThread (threading.Thread):   #创建一个自定义线程类mythread,继承Thread

def __init__(self,name):
    """
    重新init方法
    :param name: 线程名
    """
    super(myThread, self).__init__(name=name)
    # self.lock=lock

    print '线程名'+name

def run(self):
    """
    重新run方法,这里面写我们的逻辑
    :return:
    """
    i=0
    while i<10:
        print i
        time.sleep(1)
        i=i+1


if __name__=='__main__':
    t=myThread('mythread')
    t.start()

输出:

线程名线程
0
1
2
3
4
5
6
7
8
9

线程同步

如果两个线程同时访问同一个数据的时候,可能会出现无法预料的结果,这时候我们就要用到线程同步的概念.

上面我们讲到thread模块的时候,已经使用了线程锁的概念,thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire方法和release方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间.

下面我们来举例说明,我们需要实现3个线程同时访问一个全局变量,并且改变这个变量:

1.不加锁的情况
import threading,time

lock=threading.Lock()   #全局的锁对象
temp=0    #我们要多线程访问的全局属性

class myThread (threading.Thread):   #创建一个自定义线程类mythread,继承Thread

    def __init__(self,name):
        """
        重新init方法
        :param name: 线程名
        """
        super(myThread, self).__init__(name=name)
        # self.lock=lock

        print '线程名'+name

    def run(self):
        """
        重新run方法,这里面写我们的逻辑
        :return:
        """
        global temp,lock

        i=0
        while i<2:   #这里循环两次累加全局变量,目的是增加出错的概率

            temp=temp+1  #在子线程中实现对全局变量加1

            print self.name+'--temp=='+str(temp)
            i=i+1

    if __name__=='__main__':


        t1=myThread('线程1')
        t2=myThread('线程2')
        t3=myThread('线程3')

        #创建三个线程去执行访问
        t1.start()
        t2.start()
        t3.start()

执行结果(由于程序运行很快,你多运行几次就可能会出现以下结果): 我们可以发现,线程1和线程2同时访问到了变量,导致打印出现对等情况

线程名线程1
线程名线程2
线程名线程3
线程1--temp==1线程2--temp==2
线程1--temp==3

线程2--temp==4
线程3--temp==5
线程3--temp==6
2.加锁情况
import threading,time

lock=threading.Lock()   #全局的锁对象
temp=0    #我们要多线程访问的全局属性

class myThread (threading.Thread):   #创建一个自定义线程类mythread,继承Thread

    def __init__(self,name):
        """
        重新init方法
        :param name: 线程名
        """
        super(myThread, self).__init__(name=name)
        # self.lock=lock

        print '线程名'+name

    def run(self):
        """
        重新run方法,这里面写我们的逻辑
        :return:
        """
        global temp,lock

        if lock.acquire():  #这里线程进来访问变量的时候,锁定变量
            i = 0
            while i < 2:  # 这里循环两次累加全局变量,目的是增加出错的概率

                temp = temp + 1  # 在子线程中实现对全局变量加1

                print self.name + '--temp==' + str(temp)
                i = i + 1

            lock.release()  #访问完毕,释放锁让另外的线程访问



if __name__=='__main__':


    t1=myThread('线程1')
    t2=myThread('线程2')
    t3=myThread('线程3')

    #创建三个线程去执行访问
    t1.start()
    t2.start()
    t3.start()

运行结果(不管运行多少次,都不会出现同时访问的情况):

线程名线程1
线程名线程2
线程名线程3
线程1--temp==1
线程1--temp==2
线程2--temp==3
线程2--temp==4
线程3--temp==5
线程3--temp==6

线程同步很多地方都会用到,比如抢票,抽奖,我们需要对一些资源进行锁定,以防止多线程访问的时候出现不可预知的情况.

线程队列

python中的队列用到了Queue模块,该模块提供了同步的,安全的对序列,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue.这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的通信

Queue模块中的常用方法:

  • Queue.qsize() 返回队列的大小
  • Queue.empty() 如果队列为空,返回True,反之False
  • Queue.full() 如果队列满了,返回True,反之False
  • Queue.full 与 maxsize 大小对应
  • Queue.get([block[, timeout]])获取队列,timeout等待时间
  • Queue.get_nowait() 相当Queue.get(False)
  • Queue.put(item) 写入队列,timeout等待时间
  • Queue.put_nowait(item) 相当Queue.put(item, False)
  • Queue.task_done() 在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号
  • Queue.join() 实际上意味着等到队列为空,再执行别的操作

例子:
tags=['one','tow','three','four','five','six']

q=Queue.LifoQueue()   #先入先出队列
for t in tags:
q.put(t)   #将数组数据加入队列
for i in range(6):
    print q.get()    #取出操作可以放在不同的线程中,不会出现同步的问题

结果:

six
five
four
three
tow
one

Q&A

这章的多线程就到这里了,我们主要讲述了他的基本用法,更多的用法我们可以在以后的开发过程中,根据自己逻辑去设计.

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

推荐阅读更多精彩内容

  • Python 面向对象Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对...
    顺毛阅读 4,207评论 4 16
  • Python 多线程 多线程类似于同时执行多个不同程序,多线程运行有如下优点: 使用线程可以把占据长时间的程序...
    今早上阅读 345评论 0 0
  • 引言&动机 考虑一下这个场景,我们有10000条数据需要处理,处理每条数据需要花费1秒,但读取数据只需要0.1秒,...
    chen_000阅读 500评论 0 0
  • 欲望是一个人对幸福生活的向往,是人极度想得到自己喜欢的的物质的体现。人人都有欲望?你的欲望得到满足了吗? 欲望是人...
    烂泥巴糊不上墙阅读 810评论 0 1
  • 1.《白夜行》 作者 东野圭吾 作者简介: 东野圭吾,日本推理小说作家。1985年,凭借《放学后》获得第31回江户...
    _朝歌阅读 315评论 0 4