在正式介绍线程之前我们,首先要了解一下进程与线程的关系,以及分线程等等种种概念
进程:每个程序都会有一个进程负责管理程序各个功能的执行,进程只会有一个。
线程:每个进程里面至少有一个线程,称为主线程,除此之外,还会有其他的线程,称之为分线程。
如果读者对于线程基本概念不太了解,可以参看一下知名博主阮一峰的一篇博客:《进程与线程的一个简单解释》。
1.线程的基本操作。
Python中多线程主要有两个模块,_thread和threading模块,前者底层一点,后者是我们常用的模块,能够满足绝大部分的编程需求。我们主要围绕threading这个模块展开介绍,线程的启动运行,需要去创建一个Thread对象,然后调用该对象的start()方法。
首先我们来我们来看一下当前进程的代码。
import threading
print('当前线程为',threading.current_thread().name)
点击运行,输出结果为:当前线程为 MainThread。这个就是当前线程的名称。
import threading
def fun(n):
while n > 0:
print("线程name:", threading.current_thread().name, "参数n:", n)
n -= 1
t = threading.Thread(target=fun, args=(5,))
t.start()
#线程name: Thread-1 参数n: 5
#线程name: Thread-1 参数n: 4
#线程name: Thread-1 参数n: 3
#线程name: Thread-1 参数n: 2
#线程name: Thread-1 参数n: 1
在Thread中形参中,形参target传入函数名,args传入函数对应的参数,这个参数必须是可迭代的对象,如果是一个元组,且元组内只有一个参数,那么必须在参数后加上逗号,此逗号不可省略。
开辟新的线程。
import threading
def myThread():
print('位置1', threading.current_thread().name)
print('位置2', threading.current_thread().name)
myThread()
sub_thread=threading.Thread(target=myThread,name='newThread')
sub_thread.start()
#位置1 MainThread
#位置2 MainThread
#位置1 newThread
#位置2 newThread
threading.Thread开辟一个新的线程,name为新线程的名称。
import threading
def myThread():
print('位置1', threading.current_thread().name)
print('位置2', threading.current_thread().name)
sub_thread=threading.Thread(target=myThread,name='newThread')
sub_thread.start()
# sub_thread.join()
print('outside1',threading.current_thread().name)
print('outside2',threading.current_thread().name)
#位置1 newThread
#outside1 MainThread
#位置2 newThread
#outside2 MainThread
输出的结果有可能是交叉的,也有可能是不交叉的,当程序运行的时候,主线程和分线程的任务是交叉进行的,彼此不会相互影响。
把代码中的sub_thread.join()解掉注释,输出的结果是
只会是这一种情况。jion()的作用就是让主线程去等待其他的线程,主线程会在所有的的线程都结束之后,再运行主线程。
2.线程锁
当我们用多个线程去修改同一个数据的时候,这个时候对出现线程与线程之间的竞争,任何一个线程的改变,都会对其他的线程造成影响,如果我们想要得到我们想要的结果,在其中一个线程运行完之前,其他的线程都不能先对数据进行改动,那么我们就需要对这个线程去加上一个线程锁。
import threading
import time
import random
count=0
def get_money(money):
global count
count+=money
count+=money
count-=money
def lock_thread(money):
time.sleep(random.randint(1,3))
print('当前线程为', threading.current_thread().name)
get_money(money)
time.sleep(random.randint(1, 3))
print('当前线程为', threading.current_thread().name)
thread1=threading.Thread(target=lock_thread,name='thread1',args=(1000,))
thread2=threading.Thread(target=lock_thread,name='thread2',args=(2000,))
thread1.start()
thread2.start()
#当前线程为 thread2
#当前线程为 thread1
#当前线程为 thread1
#当前线程为 thread2
如上代码,输出的结果会多种多样的,毫无顺序可言,为了想要得到我们想要的结果,我们要对此加上一个线程锁。
lock = threading.Lock() # Lock对象
lock.acquire() # 锁定
lock.release() # 解锁
当状态为解锁时,acquire()将状态更改为锁定并立即返回。当状态被锁定时,acquire()块直到对另一个协程中的release()的调用将其改变为解锁,然后acquire()调用将其重置为锁定并返回。
import threading
import time
import random
count=0
def get_money(money):
global count
count+=money
count+=money
count-=money
lock=threading.Lock()
def lock_thread(money):
lock.acquire()
time.sleep(random.randint(1,3))
print('当前线程为', threading.current_thread().name)
get_money(money)
time.sleep(random.randint(1, 3))
print('当前线程为', threading.current_thread().name)
# 解锁
lock.release()
thread1=threading.Thread(target=lock_thread,name='thread1',args=(1000,))
thread2=threading.Thread(target=lock_thread,name='thread2',args=(2000,))
thread1.start()
thread2.start()
# print('hello world')
#当前线程为 thread1
#当前线程为 thread1
#当前线程为 thread2
#当前线程为 thread2
如上代码所示,输出的结果只会有这一个形式,必须thread1完成后,thread2才会执行。在代码下方有一个被注释掉的主线程, print('hello world')。解开注释后,将会先输出hello world,然后再输出上方的结果。与join相比,lock更加注重局部,某一个线程还没有走完,其他的线程就不能使用。join注重的是整体,线程1没有执行完,线程2就不能执行。
3.线程队列
import queue
# 创建一个线程队列
# 队列FIFO,frist in frist out
q=queue.Queue()
for i in range(5):
# 将内容放入到线程队列中
q.put(i)
while not q.empty():
print(q.get())
#0
#1
#2
#3
#4
这种队列是属于先进先出类型的。
import queue
p=queue.LifoQueue()
# LIFO last in frist out 后进先出
for i in range(5):
p.put(i)
while not p.empty():
print(p.get())
#4
#3
#2
#1
#0
这种队列是属于后进先出类型的。
4.介绍yield与return之间有何区别
yield与return的相同点,都是可以返回函数执行的结果,不同点就是return在返回结果后,结束函数的运行,而yield则是让函数变成了一个生成器,返回值后,继续向下运行。通过yield传递值得方法是一个迭代的对象。下面看例子。
def test1(num):
print('123456')
return num
print('return结束')
num=test1('9')
print(num)
#123456
#9
这个是return 的用法,return下面的print('return结束')并没有输出。
def test2(n):
for i in range(n):
yield i*i
print('结束')
for i in test2(4):
print(i)
#0
#结束
#1
#结束
#4
#结束
#9
#结束
#16
#结束
可以看到每次得到一个返回之后,依然向下运行,输出结束两字。
yield相比于return,可以使用的更加灵活,反应也会更加迅速。