Code前端首页关于Code前端联系我们

Python 8学习教程:多线程与队列

terry 2年前 (2023-09-25) 阅读数 48 #后端开发

线程的概念

很多编程语言像java、oc等都会有线程的概念。线程的用途非常广泛,给我们开发带来了巨大的好处。它带来了很多安慰。主要用于串行或并行逻辑处理。例如,当点击按钮时,我们可以通过进度条控制线程的运行时间,以实现更好的用户交互。

每个独立线程包含正在执行的程序的输入、执行的顺序序列以及正在执行的程序的输出。线程必须存在于程序中,不能独立于程序运行!

每个线程都有自己的一组 CPU 内存,称为线程上下文,它反映了线程最后执行的 CPU 寄存器的状态。命令指针和堆栈指针寄存器是线程上下文中两个最重要的寄存器。线程总是获取进程中的上下文。在运行时,这些地址用于指向线程所属进程的地址空间中的内存。

Python 线程

Python 中主要有两个线程模块,thread 和 thread ing。 thread 模块在最低级别提供最基本的线程功能和简单的锁定。 threading模块是thread模块的高级封装,提供各种线程属性和方法。下面我们将对这两个模块进行一一分析。

thread 模块(不推荐)

thread 模块常用函数方法:

函数名称描述
start_new_thread (function, args, kwargs=None)G 创建新线程,function 为执行线程函数名,args 为函数参数(元组类型),kwargs 为可选参数
allocate_lock() 分配一个locktype类型的线程锁对象
exit()线程退出
_count()返回线程数量。请注意,不涉及主线程,因此在主线程上运行此方法将返回 0
lockedlocktype 锁。返回 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模块提供的线程操作极其有限,使用起来非常不灵活。下面我们介绍一下他的同胞thread模块。

thread模块(推荐)

thread模块是thread的完美。它有一套成熟的线程方法,基本可以完成我们需要的所有线程操作

Putting常用方法:

  • Putting.currentThread():返回当前线程变量
  • Putting.enumerate():返回包含正在运行的线程的列表。执行是指线程启动后至结束前,但线程启动前和结束后除外。
  • Pingting.activeCount():返回正在运行的线程数,与len(Pingting.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对象可以实现简单的线程同步。这两个对象都有一个 get 方法和一个 release 方法。对于那些一次只有一个线程需要管理的数据,可以将Operation放在get和release方法之间。

我们举下面的例子。我们需要实现三个线程来同时访问全局变量并修改该变量:

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使用了Queue模块,它提供了同步且安全的对的排序,包括FIFO(先进先出)队列、LIFO(后进先出)队列LifoQueue和优先级priorityQueue队列。这些队列实现锁定原语,并且可以直接跨多个线程使用。队列可以用来进行线程之间的通信

Queue 模块中的常用方法:

  • Queue.qsize() 返回队列的大小
  • Queue.empty() 如果队列为空则返回 True,否则返回 True False
  • Queue.full() 如果队列已满则返回 True,否则返回 False
  • Queue.full 对应 size
  • Queue.get([block[, timeout]]) get queue 的最大大小,等待时间超时
  • Queue.get_nowait() 相当于 Queue.get(False)
  • Queue.put(item) 写入队列超时等待时间
  • Queue.put_nowait(item) 相当于 Queue。 put (item, False)
  • Queue.task_done() 完成一个任务后,Queue.task_done()函数会向任务完成的队列发送一个信号
  • Queue.join()实际上意味着等待将队列清空,然后再执行其他操作

示例:
tags=['one','tow','三','四','五','六']

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

结果:

six
five
four
three
tow
one

作者:momoxiaomming
链接:https://juejin.im/post/5a90c136f265da4e8409492a
来源:掘金
版权归作者所有。商业转载请联系作者获得许可。非商业转载请注明来源。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门