多线程(一)
1.多进程补充(生产者消费者模型)
生产者消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
为什么要使用生产者和消费者模式?
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式?
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
基于队列的生产者消费者模型
from multiprocessing import Process,Queue
import time,random
def producer(q):
for i in range(1,6):
time.sleep(random.randint(1,3))
res = f'{i}号食物'
q.put(res)
print(f'生产者生产了{res}')
def consumer(q):
while 1:
try:
food = q.get(timeout = 3)
time.sleep(random.randint(1,3))
print(f'消费者消费了{food}')
except Exception:
return
if __name__ == '__main__':
q = Queue()
p1 = Process(target = producer,args = (q,))
p2 = Process(target = consumer,args = (q,))
p1.start()
p2.start()
2.线程
开启一个进程:在内存中开启一个进程空间,将主进程的数据资料全部复制一份,线程会执行里面的代码
进程只是用来把资源集合到一起(进程只是一个资源单位,或者说资源集合),线程才是cpu上的执行单位. 进程是资源单位,线程是执行单位
多线程:在一个进程里存在多个控制线程,多个控制线程共享该进程的地址空间
主线程和子线程之间没有地位,但是主线程要等其他非守护线程执行完成后,才能结束本进程
进程vs线程:
1.开启进程的开销非常大,比开启线程的开销大很多
2.开启线程的速度非常快,比进程快几十到上百倍
3.线程之间可以共享数据,进程之间需要借助队列等方法实现通信
2.开启线程的两种方式
第一种方式
from threading import Thread
import time
def task(name):
print(f'{name} is running')
time.sleep(1)
print(f'{name} is done')
if __name__ == '__main__':
t1 = Thread(target = task,args = ('tom',))
ti.start()
print("主线程运行") #线程没有主次之分
第二种方式
from threading import Thread
import time
class MyThread(Thread):
def __init__(self,name):
super().__init__()
self.name = name
def run(self):
print(f'{self.name} is running')
time.sleep(1)
print(f'{self.name} is done')
if __name__ == '__main__':
t1 = MyThread('tom')
t1.start()
print('主线程运行')
3.多进程与多线程对比
1.开启速度
多进程:
from multiprocessing import Process
import time
def task():
print("hello")
if __name__ == '__main__':
start_time = time.time()
p = Process(target = task)
p.start()
print(f"主线程/主进程:{time.time() - start_time}")
#主线程/主进程:0.022937774658203125
#hello
多线程:
from threading import Thread
import time
def task():
print('hello')
if __name__ == '__main__':
start_time = time.time()
t = Thread(target = task)
t.start()
print(f"主线程:{time.time() - start_time}")
#hello
#主线程:0.0009982585906982422
2.pid
多进程
from multiprocessing import Process
import os
def task():
print(f"子进程{os.getpid()}")
if __name__ == '__main__':
p = Process(target = task)
p.start()
print(f'主进程:{os.getpid()}')
#主进程:26628
#子进程19416
多线程:
from threading import Thread
import os
def task():
print(f'子线程{os.getpid()}')
if __name__ == '__main__':
t = Thread(target = task)
t.start()
print(f'主线程{os.getpid()}')
#子线程11740
#主线程11740
开多个进程,每个进程都有不同的pid,在主进程下开启多个线程,每个线程都跟主进程的pid一样
3.同一个进程内线程共享内部数据
from threading import Thread
x = 3
def task():
global x
x = 100
if __name__ == '__main__':
t1 = Thread(target=task)
t1.start()
t1.join()
print(f'===主线程{x}')
#===主线程100
同一进程内的资源数据对于这个进程的多个线程来说是共享的.
4.线程的相关其他方法
from threading import Thread,currentThread,enumerate,activeCount
import os,time
def work():
time.sleep(3)
print("hello")
if __name__ == '__main__':
#在主进程下开启线程
t1 = Thread(target = work,name = '线程1')
t2 = Thrad(target = work)
t1.start()
t2.start()
print("主线程")
print(t1.isAlive) #判断线程是否活着
print(t1.getName) #获取线程名
t2.setName('子线程2')
print(t2.name) #获取线程名
print(currentThread()) #获取当前线程的对象
print(enumerate()) #返回一个列表,包含所有的线程对象
print(activeCount()) #返回正在运行的线程数量
Thread实例对象的方法
# isAlive(): 返回线程是否活动的。
# getName(): 返回线程名。
# setName(): 设置线程名。
threading模块提供的一些方法:
# threading.currentThread(): 返回当前的线程变量。
# threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
# threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
5.join方法与守护线程
join:阻塞,告知主线程要等待子线程执行完毕后再执行主线程
from threading import Thread
import time
def task(name):
print(f"{name} is running")
time.sleep(1)
print(f"{name} is done")
if __name__ == '__main__':
start_time = time.time()
t1 = Thread(target = task,args = ('tom',))
t2 = Thread(target = task,args = ('tony',))
t1.start()
t1.join()
t2.start()
t2.join()
print(f"主线程运行:{time.time()-start_time}")
#tom is running
#tom is done
#tony is running
#tony is done
#主线程运行:2.002790927886963
守护线程:
from threading import Thread
import time
def task(name):
print(f"{name} is running")
time.sleep(1)
print(f"{name} is done")
if __name__ == '__main__':
start_time = time.time()
t = Thread(target = task,args = ('tom',))
#t.setDaemon(True) 等同于t.daemon=True必须在t.start()之前设置
t.daemon = True
t.start()
print(f"主线程运行:{time.time()-start_time}")
#tom is running
#主线程运行:0.0
拓展:
from threading import Thread
import time
def foo():
print(123) # 1
time.sleep(1)
print("end123") # 4
def bar():
print(456) # 2
time.sleep(3)
print("end456") # 5
t1=Thread(target=foo)
t2=Thread(target=bar)
t1.daemon=True
t1.start()
t2.start()
print("main-------") # 3
#123
#456
#main-------
#end123
#end456
无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁
需要强调的是:运行完毕并非终止运行
#1.对主进程来说,运行完毕指的是主进程代码运行完毕
#2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
详细解释:
#1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束,
#2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。
6.互斥锁
多线程的同步锁与多进程的同步锁是一个道理,就是多个线程抢占同一个数据(资源)时,我们要保证数据的安全,合理的顺序。
不加锁抢占同一个资源:
from threading import Thread
import time
x = 100
def task():
global x
temp = x
time.sleep(0.1)
temp -= 1
x = temp
if __name__ == '__main__':
t_l1 = []
for i in range(100):
t = Thread(target=task)
t_l1.append(t)
t.start()
for i in t_l1:
i.join()
print(f'主{x}')
#主99
加锁保证数据安全:
from threading import Thread
from threading import Lock
import time
x = 100
def task(lock):
lock.acquire()
global x
temp = x
time.sleep(0.01)
temp -= 1
x = temp
lock.release()
if __name__ == '__main__':
lock = Lock()
l1 = []
for i in range(100):
t = Thread(target=task,args=(lock,))
l1.append(t)
t.start()
time.sleep(3)
print(f'主{x}')
#主0