1.概念
#gil global interpreter lock (cpython)
#python中一个线程对应于c语言中的一个线程
#gil使得同一个时刻只有一个线程在一个cpu上执行字节码, 无法将多个线程映射到多个cpu上执行(而C和java是可以的)
#有了GIL是不是就是代表线程是绝对安全的? 不是
#gil会根据执行的字节码行数以及时间片释放gil,gil在遇到io的操作时候主动释放
#gil global interpreter lock (cpython)
#python中一个线程对应于c语言中的一个线程
#gil使得同一个时刻只有一个线程在一个cpu上执行字节码, 无法将多个线程映射到多个cpu上执行(而C和java是可以的)
#有了GIL是不是就是代表线程是绝对安全的? 不是
#gil会根据执行的字节码行数以及时间片释放gil,gil在遇到io的操作时候主动释放
# import dis
# def add(a):
# a = a+1
# return a
#
# print(dis.dis(add)) #反编译
total = 0
def add():
#1. dosomething1
#2. io操作
# 1. dosomething3
global total
for i in range(1000000):
total += 1
def desc():
global total
for i in range(1000000):
total -= 1
import threading
thread1 = threading.Thread(target=add)
thread2 = threading.Thread(target=desc)
thread1.start()
thread2.start()
thread1.join()
thread2.join()
print(total)
-314650
2.多线程
#对于io操作来说,多线程和多进程性能差别不大
爬取网页是一个socket的过程,socket属于io编程
虽然有GIL的存在,同一时刻只能有一个线程在一个cpu上执行,但是python的内部的调度的原因,会在以字节码行数或时间片的情况下,IO操作的情况下,对线程进行切换(释放GIL)。相当于多个线程同时在运行。
#对于非io操作来说
而对于不是IO的操作,python中的多线程是通过时间片的切换来实现,虽然多个线程可以在多个cpu上执行,但同一时刻只能有一个线程在一个cpu上执行。而对于c和java来说,可以将多个线程同时映射到多个cpu上。这样python的速度就有所下降。
#对于io操作来说,多线程和多进程性能差别不大
#1.通过Thread类实例化
#thread1.setDaemon(True) #守护线程
#thread1.join() #等待线程执行完毕在退出
import time
import threading
def get_detail_html(url):
print("get detail html started")
time.sleep(2)
print("get detail html end")
def get_detail_url(url):
print("get detail url started")
time.sleep(4)
print("get detail url end")
#2. 通过集成Thread来实现多线程
class GetDetailHtml(threading.Thread):
def __init__(self, name):
super().__init__(name=name)
def run(self):
print("get detail html started")
time.sleep(2)
print("get detail html end")
class GetDetailUrl(threading.Thread):
def __init__(self, name):
super().__init__(name=name)
def run(self):
print("get detail url started")
time.sleep(4)
print("get detail url end")
if __name__ == "__main__":
thread1 = GetDetailHtml("get_detail_html")
thread2 = GetDetailUrl("get_detail_url")
start_time = time.time()
thread1.start()
thread2.start()
thread1.join()
thread2.join()
#当主线程退出的时候, 子线程kill掉
print ("last time: {}".format(time.time()-start_time))
3.线程间的通信
1.通过共享变量
2.通过queue
4.线程间的同步(加锁)
1.使用锁会影响程序的性能,锁需要释放
2.为了提高性能,引入了不同的锁
5.死锁
1)情况1(从同一个锁的角度讲)
lock.acquire()
lock.acquire()
当获得锁之后,如果不release,就会一直阻塞;上面的代码中,在第一行多的锁之后,我们又在第二行加了一把锁,由于第一把锁已经加上,别的线程不能再执行,无法释放锁,而这个线程又在下一行加锁,就会一直阻塞
2.从资源获取的角度讲(多个锁)
所谓死锁: 是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
import threading
import time
mutexA = threading.Lock()
mutexB = threading.Lock()
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
self.fun1()
self.fun2()
def fun1(self):
mutexA.acquire() # 如果锁被占用,则阻塞在这里,等待锁的释放
print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
mutexB.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
mutexB.release()
mutexA.release()
def fun2(self):
mutexB.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResB",time.time()))
time.sleep(0.2)
mutexA.acquire()
print ("I am %s , get res: %s---%s" %(self.name, "ResA",time.time()))
mutexA.release()
mutexB.release()
if __name__ == "__main__":
print("start---------------------------%s"%time.time())
for i in range(0, 10):
my_thread = MyThread()
my_thread.start()
出现死锁不停竞争,程序卡住。
6.Condition的用法【线程间的同步】
import threading
#条件变量, 用于复杂的线程间同步
# class XiaoAi(threading.Thread):
# def __init__(self, lock):
# super().__init__(name="小爱")
# self.lock = lock
#
# def run(self):
# self.lock.acquire()
# print("{} : 在 ".format(self.name))
# self.lock.release()
#
# self.lock.acquire()
# print("{} : 好啊 ".format(self.name))
# self.lock.release()
#
# class TianMao(threading.Thread):
# def __init__(self, lock):
# super().__init__(name="天猫精灵")
# self.lock = lock
#
# def run(self):
#
# self.lock.acquire()
# print("{} : 小爱同学 ".format(self.name))
# self.lock.release()
#
# self.lock.acquire()
# print("{} : 我们来对古诗吧 ".format(self.name))
# self.lock.release()
#通过condition完成协同读诗
class XiaoAi(threading.Thread):
def __init__(self, cond):
super().__init__(name="小爱")
self.cond = cond
def run(self):
with self.cond:
self.cond.wait()
print("{} : 在 ".format(self.name))
self.cond.notify()
self.cond.wait()
print("{} : 好啊 ".format(self.name))
self.cond.notify()
self.cond.wait()
print("{} : 君住长江尾 ".format(self.name))
self.cond.notify()
self.cond.wait()
print("{} : 共饮长江水 ".format(self.name))
self.cond.notify()
self.cond.wait()
print("{} : 此恨何时已 ".format(self.name))
self.cond.notify()
self.cond.wait()
print("{} : 定不负相思意 ".format(self.name))
self.cond.notify()
class TianMao(threading.Thread):
def __init__(self, cond):
super().__init__(name="天猫精灵")
self.cond = cond
def run(self):
with self.cond:
print("{} : 小爱同学 ".format(self.name))
self.cond.notify() #通知wait()的线程方法的启动,前提是先有wait()等待
self.cond.wait()
print("{} : 我们来对古诗吧 ".format(self.name))
self.cond.notify()
self.cond.wait()
print("{} : 我住长江头 ".format(self.name))
self.cond.notify()
self.cond.wait()
print("{} : 日日思君不见君 ".format(self.name))
self.cond.notify()
self.cond.wait()
print("{} : 此水几时休 ".format(self.name))
self.cond.notify()
self.cond.wait()
print("{} : 只愿君心似我心 ".format(self.name))
self.cond.notify()
self.cond.wait()
if __name__ == "__main__":
from concurrent import futures
cond = threading.Condition()
xiaoai = XiaoAi(cond)
tianmao = TianMao(cond)
#启动顺序很重要
#在调用with cond之后才能调用wait或者notify方法
#condition有两层锁, 一把底层锁会在线程调用了wait方法的时候释放, 上面的锁会在每次调用wait的时候分配一把并放入到cond的等待队列中,等到notify方法的唤醒
xiaoai.start()
tianmao.start()
7.#Semaphore 是用于控制进入数量的锁【线程间的同步】
#Semaphore 是用于控制进入数量的锁
#文件, 读、写, 写一般只是用于一个线程写,读可以允许有多个
#做爬虫
import threading
import time
class HtmlSpider(threading.Thread):
def __init__(self, url, sem):
super().__init__()
self.url = url
self.sem = sem
def run(self):
time.sleep(2)
print("got html text success")
self.sem.release() #消费 加一
class UrlProducer(threading.Thread):
def __init__(self, sem):
super().__init__()
self.sem = sem
def run(self):
for i in range(20):
self.sem.acquire() #产生 减一 为零就阻塞
html_thread = HtmlSpider("https://baidu.com/{}".format(i), self.sem)
html_thread.start()
if __name__ == "__main__":
sem = threading.Semaphore(3)
url_producer = UrlProducer(sem)
url_producer.start()
8.线程池的实现方式 ThreadPoolExecutor
#实现线程池的方式一
from concurrent.futures import ThreadPoolExecutor, as_completed, wait, FIRST_COMPLETED
from concurrent.futures import Future
from multiprocessing import Pool
#未来对象,task的返回容器
#线程池, 为什么要线程池【线程池实现程序的并发】,虽然用Condition也能实现,但线程池提供更多的功能
#主线程中可以获取某一个线程的状态或者某一个任务的状态,以及返回值
#当一个线程完成的时候我们主线程能立即知道
#futures可以让多线程和多进程编码接口一致
import time
def get_html(times):
time.sleep(times)
print("get page {} success".format(times))
return times
executor = ThreadPoolExecutor(max_workers=2)
#通过submit函数提交执行的函数到线程池中, submit 是立即返回
# task1 = executor.submit(get_html, (3))
# task2 = executor.submit(get_html, (2))
#要获取已经成功的task的返回
urls = [3,2,4]
all_task = [executor.submit(get_html, (url)) for url in urls]
wait(all_task, return_when=FIRST_COMPLETED)#加入自己的执行逻辑,第一个url中的任务执行完毕才往下执行
print("main")
# for future in as_completed(all_task):#这个是谁先执行完毕谁就输出结果,as_completed()方法用来取结果
# data = future.result()
# print("get {} page".format(data))
#通过executor的map获取已经完成的task的值,map返回的结果的顺序和url中的顺序一样
# for data in executor.map(get_html, urls):
# print("get {} page".format(data))
# #done方法用于判定某个任务是否完成
# print(task1.done())
# print(task2.cancel())
# time.sleep(3)
# print(task1.done())
#
# #result方法可以获取task的执行结果
# print(task1.result())
9.多进程与多线程的比较
https://www.cnblogs.com/haiyan123/p/7461294.html
import time
from concurrent.futures import ThreadPoolExecutor, as_completed #线程池
from concurrent.futures import ProcessPoolExecutor #进程池
#多进程编程
#耗cpu的操作,用多进程编程, 对于io操作来说, 使用多线程编程,进程切换代价要高于线程
#1. 对于耗费cpu的操作,多进程由于多线程
# def fib(n):
# if n<=2:
# return 1
# return fib(n-1)+fib(n-2)
#
# if __name__ == "__main__":
# with ThreadPoolExecutor(3) as executor:
# all_task = [executor.submit(fib, (num)) for num in range(25,40)]
# start_time = time.time()
# for future in as_completed(all_task):
# data = future.result()
# print("exe result: {}".format(data))
#
# print("last time is: {}".format(time.time()-start_time))
#2. 对于io操作来说,多线程优于多进程
def random_sleep(n):
time.sleep(n)
return n
#在多线程和多进程的执行下,在windows系统中,必须在__name__=='__main__'下进行,不然会报错,linux下不用
if __name__ == "__main__":
with ProcessPoolExecutor(3) as executor:
all_task = [executor.submit(random_sleep, (num)) for num in [2]*30]
start_time = time.time()
for future in as_completed(all_task):
data = future.result()
print("exe result: {}".format(data))
print("last time is: {}".format(time.time()-start_time))
10.multiprocessing
# progress = multiprocessing.Process(target=get_html, args=(2,)) #实现多进程
#实现线程池 pool = multiprocessing.Pool(multiprocessing.cpu_count())
由于GIL的存在,Python不存在多线程,要充分利用多核资源,就需要使用多进程。
multiprocessing模块是Python中的多进程管理包。
通过multiprocessing.Process对象来创建一个进程,Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。
multiprocessing与threading一样,调用同一套API。
创建多进程也可以通过继承multiprocessing.Process 的类实现run()方法来来实现,和Thread()的形式一样
# import os
# #fork只能用于linux/unix中
# pid = os.fork() #会在linux上创建一个子进程,存在两个进程,会在执行两次代码
# print("bobby")
# if pid == 0:
# print('子进程 {} ,父进程是: {}.' .format(os.getpid(), os.getppid()))
# else:
# print('我是父进程:{}.'.format(pid))
import multiprocessing #更加底层的实现多进程
#多进程编程
import time
def get_html(n):
time.sleep(n)
print("sub_progress success")
return n
if __name__ == "__main__":
# progress = multiprocessing.Process(target=get_html, args=(2,)) #实现多进程
# print(progress.pid)
# progress.start()
# print(progress.pid)
# progress.join()
# print("main progress end")
#使用线程池
pool = multiprocessing.Pool(multiprocessing.cpu_count())
# result = pool.apply_async(get_html, args=(3,)) #异步添加任务
#
# #等待所有任务完成
# pool.close()
# pool.join()
#
# print(result.get())
#imap
# for result in pool.imap(get_html, [1,5,3]): #结果与列表中的顺序一样
# print("{} sleep success".format(result))
for result in pool.imap_unordered(get_html, [1,5,3]): #谁先执行完,谁先输出结果
print("{} sleep success".format(result))
11.三个queue
from queue import Queue 线程间的通信,不适用与进程间
from multiprocessing import Process, Queue, Pool 进程间的通信,不适用于进程池(Pool)
from multiprocessing import Process,Manager (Manager创建的实例的Queue) 适用于进程池的Queue通信
进程间的通信不能通过共享变量来实现,可以通过Manager实现共享变量的进程间的通信。
进程间的通信还包括Pipe
import time
from multiprocessing import Process, Queue, Pool, Manager, Pipe
# def producer(queue):
# queue.put("a")
# time.sleep(2)
#
# def consumer(queue):
# time.sleep(2)
# data = queue.get()
# print(data)
#
# if __name__ == "__main__":
# queue = Queue(10)
# my_producer = Process(target=producer, args=(queue,))
# my_consumer = Process(target=consumer, args=(queue,))
# my_producer.start()
# my_consumer.start()
# my_producer.join()
# my_consumer.join()
#共享全局变量通信
#共享全局变量不能适用于多进程编程,可以适用于多线程
# def producer(a):
# a += 100
# time.sleep(2)
#
# def consumer(a):
# time.sleep(2)
# print(a)
#
# if __name__ == "__main__":
# a = 1
# my_producer = Process(target=producer, args=(a,))
# my_consumer = Process(target=consumer, args=(a,))
# my_producer.start()
# my_consumer.start()
# my_producer.join()
# my_consumer.join()
#multiprocessing中的queue不能用于pool进程池
#pool中的进程间通信需要使用manager中的queue
# def producer(queue):
# queue.put("a")
# time.sleep(2)
#
# def consumer(queue):
# time.sleep(2)
# data = queue.get()
# print(data)
#
# if __name__ == "__main__":
# queue = Manager().Queue(10)
# pool = Pool(2)
#
# pool.apply_async(producer, args=(queue,))
# pool.apply_async(consumer, args=(queue,))
#
# pool.close()
# pool.join()
#通过pipe实现进程间通信
#pipe的性能高于queue
# def producer(pipe):
# pipe.send("bobby")
#
# def consumer(pipe):
# print(pipe.recv())
#
# if __name__ == "__main__":
# recevie_pipe, send_pipe = Pipe()
# #pipe只能适用于两个进程
# my_producer= Process(target=producer, args=(send_pipe, ))
# my_consumer = Process(target=consumer, args=(recevie_pipe,))
#
# my_producer.start()
# my_consumer.start()
# my_producer.join()
# my_consumer.join()
def add_data(p_dict, key, value):
p_dict[key] = value
if __name__ == "__main__":
progress_dict = Manager().dict()
from queue import PriorityQueue
first_progress = Process(target=add_data, args=(progress_dict, "bobby1", 22))
second_progress = Process(target=add_data, args=(progress_dict, "bobby2", 23))
first_progress.start()
second_progress.start()
first_progress.join()
second_progress.join()
print(progress_dict)