python多进程多线程的使用

GIL 全局解释器锁

cpython解释器有,pypy解释器就没有GIL。

GIL使得同一时刻,

只有一个线程在cpu上执行字节码

也无法将多个线程映射到多个cpu上

import dis
def add(a):
	a = a + 1
	return a
	
print(dis.dis(add))
"""
多线程的时候,一个线程执行了一定的字节码后,会释放GIL锁
其他的线程,会有一个占用这个GIL锁


释放GIL锁的时机:
1.执行了一定的字节码
2.时间片
3.遇到IO操作的时候
"""

多线程编程

"""
每个进程之下,会有多个进程。
对于IO操作为主的操作来说,使用多线程较为划算。
以一个模拟的爬虫为栗子

程序:
1.一个线程去爬虫页面的url
2.一个线程去取出url,去爬取页面
"""
# mythread.py

import time
import threading


def get_detail_html(url):
    print("根据爬去的url,进入了一个页面")
    time.sleep(2)
    print("页面爬去完成")


def get_detail_url(index_url):
    print("进入首页")
    time.sleep(2)
    print("爬取更多的url")


if __name__ == "__main__":
    thread1 = threading.Thread(target=get_detail_url,args=("",))
    thread2 = threading.Thread(target=get_detail_html, args=("",))

    start_time = time.time()
    thread1.start()
    thread2.start()
    
    print(time.time()-start_time)
"""
虽然我们启动了2个线程
但是其实这个程序有三个线程
还有一个是主线程,就是我们运行的 python mythread.py 这个线程为主线程

可以看到,主线程运行完后,没有退出,而是等待子线程退出后才退出。

t.setDaemon(True) 在t.start()前加入
则主线程运行完,会关闭掉子线程,子线程中断运行,主线程退出。

t.join() 主线程会阻塞,等待线程t执行完成,然后才会向下执行。

"""

# 如果逻辑比较复杂 ,可以继承Thread类,重写run方法 。
# class A(threading.Thread)

线程间的通信-共享变量

"""
首先要了解,线程间问什么要通信。
"""

import time
import threading

detail_url_list = []


def get_detail_html(detail_url_list):
    """进入文章详情页,抓取信息"""
    while True:
        if len(detail_url_list):
            url = detail_url_list.pop()
            print("根据爬去的url,进入了一个页面")
            time.sleep(2)
            print("页面爬去完成")

            # for url in  detail_url_list:
            # 这样做,不是很合理,因为这样就又是串行抓取了
            #    print("根据爬去的url,进入了一个页面")
            #    time.sleep(2)
            #    print("页面爬去完成")


def get_detail_url(detail_url_list):
    """进入文章列表页,抓取文章的url"""
    while True:
        print("进入首页")
        time.sleep(2)
        for i in range(20):
            detail_url_list.append(f"http://edu/wenzhang/{i}")
        print("爬取更多的url")


if __name__ == "__main__":
    thread1 = threading.Thread(target=get_detail_url, args=(detail_url_list,))
    for i in range(10):
        html_thread = threading.Thread(target=get_detail_html, args=(detail_url_list,))
        html_thread.start()
    thread1.start()
    print("~~~~~~~~")
    
"""
当我们有许多变量需要维护的时候,比如这个list 
可以单独写一个点py文件,去存储这个变量 如/目录/variable.py里
from 目录 import variable
variable.url_list 就可以使用了

注意: 不要使用 from 目录.variable import url_list
	  因为引入url_list后,其他线程修改了后,我们是看不到也不知道被修改了的。
	  
	  
共享变量的也存在很多问题,线程安全问题
比如list.pop 并不是线程安全的,需要加锁。
如果对锁不够了解,不建议用作线程间通信
"""


线程间的通信-queue

from queue import Queue
# 设置最大值
q = Queue(maxsize=1000)
# 将q当参数传入

# 放入
q.put(obj) # block=True,timeout=None  、 如果block=False
# 如果为空,会阻塞到这里,q是线程安全的。
q.get()

q.qsize()#长度
q.empty()#判断是否为空
q.full()#判断是否满

# 异步的方法
put_nowait()
get_nowait()

q.task_done() # 会取消join阻塞
q.join() # 会一直阻塞住,如果想退出,必须在某个地方使用task_done方法

"""
首选用q,实现线程间通信
"""

线程同步 Lock

"""
线程同步,是多线程必须要面对的问题
举个栗子

a = 0
Q(a) # 给a+1
E(a) # 给a-1

多线程运行 Q E,多线程是按时间片,字节等因素切换cpu的,
Q执行过程 q1.声明全局变量a q2.赋值 q3.减法操作 q4.再赋值
E同Q e1 e2 e3 e4

如果执行顺序变为 q1 e1 qqee q4/e4
那么a 最终会为 1 或者 -1 就会出错
我们希望两个线程执行完后,数据a为0

实际线上生产环境中,web电商减库存的问题类似

"""

from threading import Lock 
#锁
lock = Lock()  # 可以将lock当参数传入到多线程中
lock.acquire() # 获取锁
# do something
lock.release() # 释放锁
"""
获取锁,和释放锁 影响性能,这是必然存在的 。

另外锁,会引起死锁的。
lock.acquire()
lock.acquire()
lock.release()
第二个lock.acquire(),会等待第一次lock.acquire()释放,就造成了死锁

死锁另一种情况,
A(a,b) acquire(a) acquire(b)
B(a,b) acquire(b) acquire(a)
现在 
A占有a 等待b资源
B占有b 等待a资源
相互等待,造成死锁

"""
from threading import RLock 
"""
另外一种会造成死锁
lock.acquire()
dosomethong(lock)
lock.release()
这种时候,就会无意陷入死锁,所以这时候python引入了可重入的锁
"""
block = RLock() # 就可以重入可

线程 锁 threading.Condition()

import threading

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()
            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__":
    cond = threading.Condition()
    xiaoai = XiaoAi(cond)
    tianmao = TianMao(cond)
	xiaoai.start()
    tianmao.start()
"""
启动顺序很重要
在调用with cond之后才能调用wait或者notify方法
condition有两层锁, 一把底层锁会在线程调用了wait方法的时候释放, 
上面的锁会在每次调用wait的时候分配一把并放入到cond的等待队列中,等到notify方法的唤醒


with self.cond:  ==

self.cond.acquire()
#智能语音音响说的所有话
self.cond.notify()
self.cond.wait()
self.cond.notify()
self.cond.wait()
self.release()
"""

线程 信号量 锁 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("http://localhost/{}".format(i), self.sem)
            html_thread.start()

if __name__ == "__main__":
    sem = threading.Semaphore(3)
    url_producer = UrlProducer(sem)
    url_producer.start()
"""
sem = threading.Semaphore(3)
sem.acquire() # 数量会-1,为0会被锁住,停在sem.acquire()
sem.release() # 释放的时候会+1
这样就可以控制 线程的并发数了。
需要注意的就是sem当做参数进行传递, 还有acquire() release() 使用的位置。

Semaphore是Condition的应用(源码解析)

这里很像线程池,使用线程池会更加简单。
"""

线程池

from concurrent.futures import ThreadPoolExecutor
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))

""" 
t.done() 判断是否完成    
t.result() 阻塞的方法,获取返回值
task2.cancel() 取消任务  取消成功返回True,执行中和执行完成是取消不掉的,会返回True
"""

线程,子线程执行完毕,主线程立即获取返回值

"""
# 线程执行完毕,主线程立即获取返回值,没有顺序的(比较下一小节)
from concurrent.futures import ThreadPoolExecutor,as_completed
executor = ThreadPoolExecutor(max_workers=2)
# 启动三个线程,并将对象放入all_task中
urls = [3,2,4]
all_task = [executor.submit(get_html, (url)) for url in urls]
# 使用 as_completed  future.result()获取返回结果
for future in as_completed(all_task):
    data = future.result()
"""

线程池 上面的另一种变种。

"""
# 线程执行完毕,主线程立即获取返回值,顺序不变(比较上一小节)
from concurrent.futures import ThreadPoolExecutor,as_completed
executor = ThreadPoolExecutor(max_workers=2)

# 通过executor的map实现上面的功能
for future in executor.map(get_html,urls):
	# urls是一个可迭代对象,每次将值传入func中,线程池中运行
    print(future.result())
    # 返回的顺序,和url的顺序是一致的
    # 和上面的不同,上面的是谁先执行完毕,打印谁。

"""

线程concurrent.futures的 wait

"""
阻塞,等待某一个或某一些线程执行完成后,才继续向下执行
from concurrent.futures import ThreadPoolExecutor, as_completed, wait
urls = [3,2,4]
all_task = [executor.submit(get_html, (url)) for url in urls]

wait(all_task)
wait 还有一个参数return_when = ALL_COMPLETED 为默认,所有
还有其他参数
有第一个执行完之后,等等,一共四个参数。
"""

多进程和多线程对比

import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from concurrent.futures import ProcessPoolExecutor
# 多进程编程
# 耗cpu的操作,用多进程编程, 对于io操作来说, 使用多线程编程,进程切换代价要高于线程

# 对于耗费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

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))

多进程

"""
from concurrent.futures import ThreadPoolExecutor, as_completed
from concurrent.futures import ProcessPoolExecutor
以上两个 多进程和多线程的 接口是一样的
"""

os.fork

import os
#fork只能用于linux/unix中
pid = os.fork()
print("bobby")
if pid == 0:
    print('子进程 {} ,父进程是: {}.' .format(os.getpid(), os.getppid())) 
   
else:
    print('我是父进程:{}.'.format(pid))
    
"""
想一下,为什么 if else 都会执行,正常情况下不是只执行其中一个吗?
是因为fork创建了一个子进程,
主进程和子进程,拥有相同的环境资源,所以可以看到打印了两边 bobby
这时候 主进程和子进程 都有if else,都会去执行,根据判断结果,执行了不同的代码而已

os.getpid()当前进程 os.getppid()当前运行进程的父进程。
"""

多进程 multiprocessing

import time
import multiprocessing

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) #None
    progress.start()
    print(progress.pid)
    progress.join()
    print("main progress end")

进程池 multiprocessing

"""
# 指明 进程数为cpu数量
pool = multiprocessing.Pool(multiprocessing.cpu_count())
# 向进程池添加任务并执行
result = pool.apply_async(get_html, args=(3,))
#等待所有任务完成
pool.close() # 关闭进程池,不再接收任务
pool.join()  # 回收
# 获取结果
result.get()
"""

进程池imap和imap_unordered


"""
for result in pool.imap(func,[1,6,3]):
	print(result) 
	# 这里result就是return回的值 
	# 这里返回顺序为列表顺序
	
	
for result in pool.imap_unordered(func,[1,6,3]):
	print(result) 
	# 这里谁先完成,谁打印
"""

进程间通信

from multiprocessing import Process, Queue, Pool, Manager, Pipe

# Queue
"""
Queue 用法同线程一样,不过要注意,是进程的Queue,此外queue不能用于pool里。
进程池pool间进程通信 用Manager().Queue(10)
"""

# Pipe 管道
"""
简化版本的Queue,只适用于两个进程
rec_pipe,send_pipe  = Pipe()
# as pipe
pipe.send("xxx")
pipe.recv() 
和socket有点像

pipe性能问题是高于queue的,某些特定情况,可以优先考虑pipe 
"""

# Manager 进程间的共享内存操作
"""
Manager().list() # 等等 可以进去看看,很多,前边线程有的进程Manager都有。

Manager().dict() 参数一样传入就可以了。
"""

你可能感兴趣的:(python多进程多线程的使用)