python多任务之线程

线程概述

多线程类似于同时执行多个不同程序,多线程运行有如下优点:

使用线程可以把占据长时间的程序中的任务放到后台去处理。

用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度

程序的运行速度可能加快

在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下我们可以释放一些珍贵的资源如内存占用等等。

线程在执行过程中与进程还是有区别的。每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

每个线程都有他自己的一组CPU寄存器,称为线程的上下文,该上下文反映了线程上次运行该线程的CPU寄存器的状态。

指令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器,线程总是在进程得到上下文中运行的,这些地址都用于标志拥有线程的进程地址空间中的内存。

线程可以被抢占(中断)。

在其他线程正在运行时,线程可以暂时搁置(也称为睡眠) -- 这就是线程的退让。

线程就是在程序运行过程中,执行程序代码的一个分支,每个运行的程序至少都有一个线程

创建多线程:thread

调用thread模块中的start_new_thread()函数来产生新线程

thread.start_new_thread ( function, args[, kwargs] )

function - 线程函数。

args - 传递给线程函数的参数,他必须是个tuple类型。

kwargs - 可选参数。

python多线程的开启

start()

demo:

import threading

import time

def task():

    time.sleep(1) 

     print("当前线程:", threading.current_thread().name)

if__name__ =='__main__':

    for_inrange(5):

         sub_thread = threading.Thread(target=task) 

         sub_thread.start()

线程之间执行是无序的

主线程会等待所有的子线程结束后才结束

主线程会等待所有的子线程结束后才结束,如果需要可以设置守护主线程

多线程的特点(共享全局变量)


threading.currentThread(): 返回当前的线程变量。

threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。

除了使用方法外,线程模块同样提供了Thread类来处理线程,Thread类提供了以下方法:

run(): 用以表示线程活动的方法。

start():启动线程活动。

join([time]): 等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。

isAlive(): 返回线程是否活动的。

getName(): 返回线程名。

setName(): 设置线程名。


自定义线程

自定义线程不能指定target,因为自定义线程里面的任务都统一在run方法里面执行

启动线程统一调用start方法,不要直接调用run方法, 因为这样不是使用子线程去执行任务

demo:

import threading

# 自定义线程类

classMyThread(threading.Thread):

# 通过构造方法取接收任务的参数

    def__init__(self, info1, info2):

    # 调用父类的构造方法

        super(MyThread, self).__init__() 

         self.info1 = info1 

         self.info2 = info2

    # 定义自定义线程相关的任务

    def test1(self):

        print(self.info1)

    deftest2(self):

        print(self.info2)

    # 通过run方法执行相关任务

    def run(self):

        self.test1() 

         self.test2()

# 创建自定义线程

my_thread = MyThread("测试1","测试2")

# 启动

my_thread.start()


资源竞争问题

多线程可以共享全局变量

多线程共享全局变量,很方便在多个线程间共享数据

由于多线程同时对全局变量进行操作,很容易出现资源竞争问题

demo:

import threading

# 定义全局变量

g_num =0

# 循环一次给全局变量加1

defsum_num1():

    for i in range(1000000):

        globalg_num 

         g_num +=1

    print("sum1:", g_num)

# 循环一次给全局变量加1

defsum_num2():

    for i in range(1000000):

        global g_num 

         g_num +=1

    print("sum2:", g_num)

if__name__ =='__main__':

# 创建两个线程

first_thread = threading.Thread(target=sum_num1) 

 second_thread = threading.Thread(target=sum_num2)

# 启动线程

first_thread.start()

# 启动线程

second_thread.start()

我们可以看到多线程同时对全局变量操作数据发生了错误

多线程同时操作全局变量导致数据可能出现错误的原因分析

两个线程first_thread和second_thread都要对全局变量g_num(默认是0)进行加1运算,但是由于是多线程同时操作,有可能出现下面情况:

在g_num=0时,first_thread取得g_num=0。此时系统把first_thread调度为”sleeping”状态,把second_thread转换为”running”状态,t2也获得g_num=0

然后second_thread对得到的值进行加1并赋给g_num,使得g_num=1

然后系统又把second_thread调度为”sleeping”,把first_thread转为”running”。线程t1又把它之前得到的0加1后赋值给g_num。

这样导致虽然first_thread和first_thread都对g_num加1,但结果仍然是g_num=1

多线程资源竞争解决办法

线程同步

线程同步: 保证同一时刻只能有一个线程去操作全局变量 同步: 就是协同步调,按预定的先后次序进行运行。如:你说完,我再说, 好比现实生活中的对讲机

多个线程同时对同一个全局变量进行操作,会有可能出现资源竞争数据错误的问题

线程同步方式可以解决资源竞争数据错误问题,但是这样有多任务变成了单任务。

互斥锁

threading模块中定义了Lock变量,这个变量本质上是一个函数,可以方便的处理锁定:

# 创建锁

mutex = threading.Lock()

# 锁定

mutex.acquire()

# 释放

mutex.release()

demo:

import threading

# 定义全局变量

g_num =0

# 创建全局互斥锁

lock = threading.Lock()

# 循环一次给全局变量加1

def sum_num1():

# 上锁

    lock.acquire()

    for i in range(1000000):

        global g_num

        g_num +=1

    print("sum1:", g_num)

# 释放锁

    lock.release()

# 循环一次给全局变量加1

def sum_num2():

# 上锁

    lock.acquire()

    for iin range(1000000):

        global g_num

        g_num +=1

    print("sum2:", g_num)

# 释放锁

    lock.release()

if __name__ =='__main__':

# 创建两个线程

    first_thread = threading.Thread(target=sum_num1)

second_thread = threading.Thread(target=sum_num2)

# 启动线程

    first_thread.start()

second_thread.start()

# 提示:加上互斥锁,那个线程抢到这个锁我们决定不了,那线程抢到锁那个线程先执行,没有抢到的线程需要等待

    # 加上互斥锁多任务瞬间变成单任务,性能会下降,也就是说同一时刻只能有一个线程去执行

当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。

每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。

线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态。

确保了某段关键代码只能由一个线程从头到尾完整地执行

锁的坏处:

多线程执行变成了包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了

锁使用不好就容易出现死锁情况

死锁

import threading

import time

# 创建互斥锁

lock = threading.Lock()

# 根据下标去取值, 保证同一时刻只能有一个线程去取值

def get_value(index):

# 上锁

    lock.acquire()

    print(threading.current_thread())

    my_list = [3,6,8,1]

# 判断下标释放越界

    if index >=len(my_list):

        print("下标越界:", index)

        return

    value = my_list[index]

    print(value)

    time.sleep(0.2)

# 释放锁

    lock.release()

if __name__ =='__main__':

# 模拟大量线程去执行取值操作

    for i in range(30):

        sub_thread = threading.Thread(target=get_value,args=(i,))

        sub_thread.start()

使用互斥锁的时候需要注意死锁的问题,要在合适的地方注意释放锁

死锁一旦发生就会造成应用的停止响应

线程优先级队列( Queue)

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() 实际上意味着等到队列为空,再执行别的操作

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