线程概述

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

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

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

程序的运行速度可能加快

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

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

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

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

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

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

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


创建多线程:thread

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

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

function - 线程函数。

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

kwargs - 可选参数。

python多线程的开启

start()

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方法, 因为这样不是使用子线程去执行任务


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()

资源竞争问题

多线程可以共享全局变量

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

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


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()

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

线程,你学废了吗?
image


活在当下
32 声望38 粉丝