进程是资源的拥有者,进程占有的资源较大,因此需要引入线程。进程是资源分配的最小单位,线程是CPU调度的最小单位。进程包含线程,一个进程至少有一个线程。
创建线程示例如下
from threading import Thread
import time
def eat(num):
for i in range(num):
print("进餐{}".format(i))
time.sleep(1)
def watchTV(num):
for i in range(num):
print("追剧{}".format(i))
time.sleep(1)
if __name__ == '__main__':
t1 = Thread(target=eat,args=(3,))
t2 = Thread(target=watchTV,args=(3,))
t1.start()
t2.start()
print("结束")
"""
进餐0
追剧0
结束
进餐1
追剧1
进餐2
追剧2
"""
这时有一个主线程和t1,t2线程。
.join方法的使用
如上代码结果,两个线程是同时执行的,如果需要一个线程执行完成后,再让另一个线程执行,可以用join()方法。
from threading import Thread
import time
def eat(num):
for i in range(num):
print("进餐中")
time.sleep(1)
def watchTV(num):
for i in range(num):
print("看电视")
time.sleep(1)
if __name__ == '__main__':
t1 = Thread(target=eat,args=(3,))
t2 = Thread(target=watchTV,args=(3,))
t1.start()
t1.join()
t2.start()
print("结束")
"""
进餐中
进餐中
进餐中
看电视
结束
看电视
看电视
"""
.setDaemon()方法
setDaemon()可以将当前线程设置成守护线程来守护主线程,当主线程结束后,守护线程也立即结束,无论守护线程是否执行。例如QQ结束之时,打开的QQ多人聊天窗口也将关闭。
from threading import Thread
import time
def eat(num):
for i in range(num):
print("进餐中")
time.sleep(1)
def watchTV(num):
for i in range(num):
print("看电视")
time.sleep(1)
if __name__ == '__main__':
t1 = Thread(target=eat,args=(3,))
t2 = Thread(target=watchTV,args=(3,))
t1.setDaemon(True)
t2.setDaemon(True)
t1.start()
t2.start()
print("结束")
"""
进餐中
看电视
结束
"""
实例方法
获取线程名:getName()
设置线程名:setName()
判断线程是否存活:isAlive()
自定义线程
编写MyThread类,让MyThread继承自threading的Thread类。然后重写run()方法。
from threading import Thread
class MyThread(Thread):
def __init__(self,num):
Thread.__init__(self)
self.num = num
def run(self):
for i in range(self.num):
print("第{}次执行".format(i))
if __name__ == '__main__':
t1 = MyThread(3)
t1.start()
"""
第0次执行
第1次执行
第2次执行
"""
.threading模块提供的方法
threading.currentThread() :返回当前线程的变量。
threading.enumerarte() : 返回正在运行的线程的列表。
threading.activeCount() :返回正在运行的线程数。
线程之间共享全局变量
线程中的全局变量可以共享,并且每个线程都可以更改全局变量
from threading import Thread
g_num = 0
def test1():
global g_num
g_num += 1
print("test1的g_num是{}".format(g_num))
def test2():
global g_num
g_num += 10
print("test2的g_num是{}".format(g_num))
if __name__ == '__main__':
t1 = Thread(target=test1)
t2 = Thread(target=test2)
t1.start()
t2.start()
print("现在g_num的值为{}".format(g_num))
"""
test1的g_num是1
test2的g_num是11
现在g_num的值为11
"""
共享全局变量带来的问题
上述代码中我们的每个线程只对数据进行了一次更改,并没有出现什么异常,但是当每个线程对数据更改几百万几千万次的时候就会出现问题。当两个线程同一时间更改数据的时候,数据只记录其中一个线程对它的更改,因此就会出现数据不准确的问题。 如下代码,结果按理说最终结果应该为20000000,但是由于共享全局变量带来的问题,导致结果小于20000000。
多线程共享全局变量带来的计算出错问题,我们可以通过同步的方式来解决。
from threading import Thread
import time
g_num = 0
def test1(num):
global g_num
for i in range(num):
g_num += 1
print("test1的g_num是{}".format(g_num))
def test2(num):
global g_num
for i in range(num):
g_num += 1
print("test2的g_num是{}".format(g_num))
if __name__ == '__main__':
t1 = Thread(target=test1,args=(10000000,))
t2 = Thread(target=test2,args=(10000000,))
t1.start()
t2.start()
time.sleep(3)
print("现在g_num的值为{}".format(g_num))
"""
test1的g_num是11832117
test2的g_num是12016733
现在g_num的值为12016733
"""
插入:同步异步的概念
同步不是同时进行的意思,同步是协同步调,有先后顺序的执行,比如A只向B表白,只有B接受之后,A才可以谈恋爱,B不接受,A就不能谈恋爱。这是两者之间具有强联系,A必须获得B的信号或者信息之后才能往下执行
异步就像平常QQ聊天一样,你要是回信息咱们就聊天,要是不回信息的话,我就做别的事情。这样的话依赖性较小。
互斥锁
当多个线程争夺一个共享数据的时候,我们同步的一种方法就是使用互斥锁。
当某个线程需要修改共享数据时,先对数据进行锁定,这样其他的线程就无法对该数据进行更改,等到该线程对数据修改完毕之后再释放数据,这样其他线程就能获得该数据,然后对该数据进行操作。这样可以保证数据的安全。
在上锁和解锁之前需要创建一个全局对象,这个对象就是锁子,然后对线程中的数据进行上锁和解锁操作。
from threading import Thread
import time
import threading
g_num = 0
def test1(num):
global g_num
lock.acquire()
for i in range(num):
g_num += 1
lock.release()
print("test1的g_num是{}".format(g_num))
def test2(num):
global g_num
lock.acquire()
for i in range(num):
g_num += 1
lock.release()
print("test2的g_num是{}".format(g_num))
lock = threading.Lock()
if __name__ == '__main__':
t1 = Thread(target=test1,args=(10000000,))
t2 = Thread(target=test2,args=(10000000,))
t1.start()
t2.start()
time.sleep(3)
print("现在g_num的值为{}".format(g_num))
"""
test1的g_num是10000000
test2的g_num是20000000
现在g_num的值为20000000
"""
死锁
锁之间的嵌套可能会出现死锁, 如果两个线程同时占有一部分资源,并且同时等待对方的资源,这个时候就会出现死锁。就像两个人每个人手里拿了一把锁,每把锁都需要两把钥匙才能打开,但是这时候两个人手中各有一把钥匙,两把钥匙放到一块儿才能打开锁子,但是A不给B钥匙,B也不给A钥匙,他们谁也打不开手中的那个需要两把钥匙才能打开的锁子。
from threading import Thread
import time
import threading
g_num = 0
def test1(num):
global g_num
lock1.acquire()
print("test1第一层")
lock1.acquire()
print("test1第二层")
for i in range(num):
g_num += 1
lock1.release()
lock1.release()
print("test1的g_num是{}".format(g_num))
def test2(num):
global g_num
lock2.acquire()
print("test2第一层")
lock2.acquire()
print("test2第二层")
for i in range(num):
g_num += 1
lock2.release()
lock2.release()
print("test2的g_num是{}".format(g_num))
lock1 = threading.Lock()
lock2 = threading.Lock()
if __name__ == '__main__':
t1 = Thread(target=test1,args=(10000000,))
t2 = Thread(target=test2,args=(10000000,))
t1.start()
t2.start()
time.sleep(3)
print("现在g_num的值为{}".format(g_num))
"""
test1第一层
test2第一层
现在g_num的值为0
"""
线程队列
import queue 导入模块
q = queue.Queue(maxsize=10) 创建队列
q.put(1) 在队列中放入数据
q.get() 删除并返回,获取不到数据就一直等待
q.qsize() 返回队列的大小
q.empty() 判断队列是否为空
q.full() 判断队列是否满
q.put_nowait(item) 放不进数据直接抛出异常
q.get_nowait(item) 得不到数据直接抛出异常
q.join() 收到q.task_done()信号再往下执行,否则一直等待。
生产者消费者模式
并发编程中,如果生产者速度很慢,那么消费者需要等待生产者。消费者如果慢的话,就需要生产者就要等待消费者。
这个时候就需要生产者和消费者之间不进行直接通讯,而是通过一个队列来进行通讯,生产者生产完后数据后直接扔到队列中,而消费者需要数据就直接从队列中取。
from threading import Thread
import queue
q = queue.Queue()
def product(name):
count = 1
while count <= 100:
q.join()
q.put(count)
print("{}正在生产第{}个数据".format(name,count))
count+=1
def consume(name):
count = 1
while count <= 100:
data = q.get()
print("{}正在获取第{}个数据".format(name,data))
q.task_done()
if __name__ == '__main__':
t1 = Thread(target=product,args=("生产者",))
t2 = Thread(target=consume,args=("消费者",))
t1.start()
t2.start()
"""
生产者正在生产第1个数据
消费者正在获取第1个数据
生产者正在生产第2个数据
消费者正在获取第2个数据
生产者正在生产第3个数据
消费者正在获取第3个数据
......
生产者正在生产第99个数据
消费者正在获取第99个数据
生产者正在生产第100个数据
消费者正在获取第100个数据
"""
q.join() 等待task_done()发送信号
q.task_done() 取完发送信号
GIL(global interpreter lock)全局解释锁