python线程与进程

九、线程与进程

9.1 多线程基本使用

  • 1.导入线程模块:import threading
  • 2.线程类Thread参数说明:Thread(lgroup [ target [, name [, args [, kwagl]]]]))
    • group:线程组,目前只能使用None
    • target:执行的目标任务名
    • argsr 以元组的方式给执行任务传参
    • kwargs: 以字典方式给执行任务传参
    • name:线程名,-般不用设置
    • daemon=True:设置为主线程的守护线程,主线程结束则子线程结束
方法功能
join()等待⼦线程结束之后,主线程继续执⾏
setDaemom()守护线程,不会等待⼦线程结束
threading.enumerate()查看当前线程的数量
start()创建并启动线程
import threading
import time
def dance():
    #获取当前线程
    current_thread=threading.current_thread()
    print("dance:",current_thread)
    for i in range(3):
        print("跳舞中...")
        time.sleep(0.2)

def sing():
    #获取当前线程
    current_thread=threading.current_thread()
    print("sing:",current_thread)
    for i in range(3):
        print("唱歌中...")
        time.sleep(0.2)

if __name__ == '__main__':
    current_thread=threading.current_thread()
    print("main_thread:",current_thread)
    sing_thread=threading.Thread(target=sing)
    sing_thread.start()
    dance_thread=threading.Thread(target=dance,name='dance')
    dance_thread.start()

9.2 线程的创建

  • 直接创建:threading.Thread(target=函数名)

  • 通过继承Thread类创建线程

    import threading
    import time
    
    class Main(threading.Thread):
        def run(self):
            for i in range(5):
                print(i)
    
    if __name__ == '__main__':
        m = Main()
        m.start()
    
    

9.3 查看线程数量

  • threading.enumerate()
import threading
import time
def demo1():
    for i in range(5):
        print('demo1--%d'%i)
        time.sleep(1)
def demo2():
    for i in range(10):
        print('demo2--%d' % i)
        time.sleep(1)

def main():

    t1 = threading.Thread(target=demo1,name = 'demo1')
    t2 = threading.Thread(target=demo2,name = 'demo2')

    t1.start()
    t2.start()

    while True:
        print(threading.enumerate())
        if len(threading.enumerate()) <= 1:
            break
        time.sleep(1)

if __name__ == '__main__':
    main()

9.4 线程间的通信

  • 在⼀个函数中,对全局变量进⾏修改的时候,是否要加global要看是否对全局变量的指向进⾏了修改,如果修改了指向,那么必须使⽤global,仅仅是修改了指向的空间中的数据,此时不⽤必须使⽤global线程是共享全局变量
  • 多线程参数-args :threading.Thread(target=test, args=(num,))
import threading
import time
# 线程间是共享的全局变量
# num = 100
num = [11,22]
def demo1(nums):
    # global num
    #
    # num += 1
    num.append(nums)
    print('demo1--%s'% str(num))
def demo2():
    print('demo2--%s' % str(num))
def main():
    t1 = threading.Thread(target = demo1,args=(33,))
    t2 = threading.Thread(target = demo2)
    t1.start()
    time.sleep(1) # 保证demo1先执行
    t2.start()
    print('main-num=%s'% str(num))

if __name__ == '__main__':
    main()

9.5 线程间的资源竞争

  • 查看CPU运行轨迹:import dis
  • 线程之间共享全局变量
import threading
import time
import dis # dis这个模块可以查看Python代码在CPU的运行轨迹
num = 0
def demo1(nums):
    global num
    for i in range(nums):
        num += 1
    print('demo1---%d'%num)
def demo2(nums):
    global num
    for i in range(nums):
        num += 1
    print('demo2---%d' % num)
def main():
    t1 = threading.Thread(target=demo1,args=(1000000,))
    t2 = threading.Thread(target=demo2,args=(1000000,))
    t1.start()
    t2.start()
    time.sleep(3)
    print('main---%d'%num)
if __name__ == '__main__':
    main()


def add_num(num):

    num += 1

print(dis.dis(add_num))

9.5.1 互斥锁

  • 当多个线程⼏乎同时修改某⼀个共享数据的时候,需要进⾏同步控制某个线程要更改共享数据时,先将其锁定,此时资源的状态为"锁定",其他线程不能改变,只到该线程释放资源,将资源的状态变成"⾮锁定",其他的线程才能 再次锁定该资源。互斥锁保证了每次只有⼀个线程进⾏写⼊操作,从⽽保证了多线程情况下数据的正确性。

  • 上锁:acquire()、解锁:release()

    import threading
    import time
    # 创建一个互斥锁
    # 互斥锁也叫做不可重复的锁。只能执行一次
    
    # mutex = threading.Lock()
    mutex = threading.RLock() # 可以上多把锁,但是加锁和解锁的数量要一一对应
    num = 0
    
    def demo1(nums):
    
        global num
    
        # 上锁
        mutex.acquire()
        mutex.acquire()
        for i in range(nums):
    
            num += 1
    
        # 解锁
        mutex.release()
        mutex.release()
        # mutex.release()
        print('demo1---%d'%num)
    
    
    def demo2(nums):
    
        global num
    
        # 上锁
        mutex.acquire()
        for i in range(nums):
            num += 1
    
        # 解锁
        mutex.release()
        print('demo2---%d' % num)
    
    
    def main():
    
        t1 = threading.Thread(target=demo1,args=(1000000,))
        t2 = threading.Thread(target=demo2,args=(1000000,))
    
    
        t1.start()
    
    
        t2.start()
    
    

9.5. 2死锁

  • 在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时等

    待对⽅的资源,就会造成死锁。

    import threading
    import time
    
    class MyThread1(threading.Thread):
        def run(self):
            # 对mutexA上锁
            mutexA.acquire()
    
            # mutexA上锁后,延时1秒,等待另外那个线程 把mutexB上锁
            print(self.name+'----do1---up----')
            time.sleep(1)
    
            # 此时会堵塞,因为这个mutexB已经被另外的线程抢先上锁了
            mutexB.acquire()
            print(self.name+'----do1---down----')
            mutexB.release()
    
            # 对mutexA解锁
            mutexA.release()
    
    class MyThread2(threading.Thread):
        def run(self):
            # 对mutexB上锁
            mutexB.acquire()
    
            # mutexB上锁后,延时1秒,等待另外那个线程 把mutexA上锁
            print(self.name+'----do2---up----')
            time.sleep(1)
    
            # 此时会堵塞,因为这个mutexA已经被另外的线程抢先上锁了
            mutexA.acquire()
            print(self.name+'----do2---down----')
            mutexA.release()
    
            # 对mutexB解锁
            mutexB.release()
    
    mutexA = threading.Lock()
    mutexB = threading.Lock()
    
    if __name__ == '__main__':
        t1 = MyThread1()
        t2 = MyThread2()
        t1.start()
        t2.start()
    

9.6 Queue线程

  • 在线程中,访问⼀些全局变量,加锁是⼀个经常的过程。如果你是想把⼀些数据存储到某个队列中,那么Python内置了⼀个线程安全的模块叫做queue模 块。Python中的queue模块中提供了同步的、线程安全的队列类,包括FIFO(先进先出)队列Queue,LIFO(后⼊先出)队列LifoQueue。这些队列 都实现了锁原语(可以理解为原⼦操作,即要么不做,要么都做完),能够在多线程中直接使⽤。可以使⽤队列来实现线程间的同步。

  • from multiprocessing import Queue #进程队列
    from queue import Queue #普通线程队列
    
  • 常用方法

    方法功能
    Queue(maxsize)创建⼀个先进先出的队列,并指定最大堆列数。
    empty()判断队列是否为空
    qsize()返回队列大小
    get([block[, timeout]])获取队列,timeout等待时间
    full()判断队列是否满了
    put()将一个数据放到队列中
    get_nowait()相当get(False)
    put_nowait(item)相当q.put(item, False)
    task_done()在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
    join()实际上意味着等到队列为空,再执行别的操作

    9.7 线程同步

    import threading
    
    class XiaoAi(threading.Thread):
    
        def __init__(self,lock):
            # super() 可以用来获取当前类的父类
            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() 可以用来获取当前类的父类
            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()
    
    
    if __name__ == '__main__':
    
        # 定义一把锁
        mutex = threading.RLock()
    
        xiaoai = XiaoAi(mutex)
        tianmao = TianMao(mutex)
    
        xiaoai.start()
        tianmao.start()
    
    

9.8 多线程爬取斗图啦

import os
import re
import threading

from queue import Queue
from urllib import request
import requests

headers = {
    'User-Agent': "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8; en-us) AppleWebKit/534.50 (KHTML, like Gecko) Version/5.1 Safari/534.50"}


class Prouducer(threading.Thread):
    """生产者img_url"""

    def __init__(self, page_url_queue, img_queue, *args, **kwargs):
        super(Prouducer, self).__init__(*args, **kwargs)
        self.page_url_queue = page_url_queue
        self.img_queue = img_queue

    def get_response(self, url, headers=None):
        try:
            resp = requests.get(url, headers=headers)
            if resp.status_code != 200:
                raise  RequestException("The request failed")
            return resp.text
        except Exception as e:
            print(e)

    def run(self):
        while not self.page_url_queue.empty():
            url = self.page_url_queue.get()  # 从队列中获取页面的url
            resp = self.get_response(url, headers)
            params = re.compile(r'<a.*?class="col-xs-4 col-md-3">.*?<img.*?data-original="(.*?)".*?alt="(.*?)".*?</a>',re.S)
            for img in re.findall(params, resp):
                self.img_queue.put(img)
            self.page_url_queue.task_done()  # 让队列计数减一


class Consumer(threading.Thread):
    """消费者img_url"""
    def __init__(self, img_queue, *args, **kwargs):
        super(Consumer, self).__init__(*args, **kwargs)
        self.img_queue = img_queue

    def get_response(self, url, headers):
        content = (request.urlopen(request.Request(url, headers=headers))).read()
        print(url)
        return content

    def save(self, url, name, content):
        name = "".join(re.split(r"[|\\/?:*<>!!]+", name))
        suffix = os.path.splitext(url)[1]
        file_name = "".join(['images/', name, suffix])
        os.makedirs("images", exist_ok=True)
        with open(file_name, "wb") as f:
            f.write(content)

    def run(self):
        while not self.img_queue.empty():
            url, name = self.img_queue.get()
            print(url, name)
            content = self.get_response(url, headers)
            self.save(url, name, content)
            self.img_queue.task_done()


class RequestException(Exception):
    """自定义异常"""
    pass
def main():
    page_url_queue = Queue()
    img_queue = Queue()

    for i in range(1, 2):
        url = f"https://www.doutula.com/zz/list?page={i}"
        page_url_queue.put(url)
    for i in range(5):
        t1 = Prouducer(page_url_queue, img_queue)
        t1.start()
        t1.join()
    for i in range(5):
        t2 = Consumer(img_queue)
        t2.start()
        t2.join()  # 让主线程阻塞(等待子线程结束在结束)


if __name__ == '__main__':
    main()

9.9 进程

  • 1.导入进程包

  • 2.导入进程包import multiprocessing

  • 3.Process进程类的说明
    Process([group [, target [, name [, args [, kwarggl]]]]])

    • group :指定进程组,目前只能使用None
    • target :执行的目标任务名
    • name :进程名字
    • args :以元组方式给执行任务传参

    • kwargs :以字典方式给执行任务传参

  • Process创建的实例对象的常用方法

    方法功能
    join()等待⼦线程结束之后,主线程继续执⾏
    terminate()不管任务是否完成,立即终止子进程
    start()创建并启动线程
  • 进程:正在执⾏的程序 ,进程之间不共享全局变量,主进程会等待所有子进程执行完才结束

  • 进程之间不共享全局变量

    import multiprocessing
    import time
    
    def task():
        for i in range(10):
            print("任务执行中")
            time.sleep(0.2)
    
    if __name__ == '__main__':
        #创建子线程
        sub_process=multiprocessing.Process(target=task)
        #把子线程设置成为守护主线程,以后主进程退出子进程直接销毁
        # sub_process.daemon=True
    
        sub_process.start()
        #主进程延迟0.5秒
        time.sleep(0.5)
    
        #让主进程退出之前先让子进程销毁
        sub_process.terminate()
        print("over")
    
    '''
    结论:主进程会等待子进程执行完成以后程序再退出
    解决方法:主进程退出子进程销毁
    1.让子进程设置成为守护主进程,主进程退出子进程销毁,子进程会依赖主进程
    2.让主进程退出之前先让子进程销毁
    '''
    
  • 主进程会等待所有的子进程执行结束再结束

    import multiprocessing
    import time
    
    def task():
        for i in range(10):
            print("任务执行中")
            time.sleep(0.2)
    
    if __name__ == '__main__':
        #创建子线程
        sub_process=multiprocessing.Process(target=task)
        #把子线程设置成为守护主线程,以后主进程退出子进程直接销毁
        # sub_process.daemon=True
    
        sub_process.start()
        #主进程延迟0.5秒
        time.sleep(0.5)
    
        #让主进程退出之前先让子进程销毁
        sub_process.terminate()
        print("over")
    
    '''
    结论:主进程会等待子进程执行完成以后程序再退出
    解决方法:主进程退出子进程销毁
    1.让子进程设置成为守护主进程,主进程退出子进程销毁,子进程会依赖主进程
    2.让主进程退出之前先让子进程销毁
    '''
    
9.9.1 多任务执行
  • 并发和并行

    • 并发:再一段时间内交替去执行任务
    • 并行:对于多核cpu处理多任务,操作系统会给cpu每个内核安排一个执行软件,多个内核是真正的一起执行软件。
  • 获取进程编号:os.getid()

  • 获取当前父进程编号:os.getppid()

    import os
    import multiprocessing
    import time
    def dance():
        #获取子进程编号
        dance_process_id = os.getpid()
        print('dance_process_id:', dance_process_id, multiprocessing.current_process)
    
        for i in range(3):
            print("跳舞中...")
            time.sleep(0.2)
            #根据进程编号杀死指定线程
            os.kill(dance_process_id,9)
    
    def sing():
        #获取父进程编号
        sing_process_parent_id = os.getppid()
        print('sing_process_prent_id:', sing_process_parent_id, multiprocessing.current_process)
        for i in range(3):
            print("唱歌中...")
            time.sleep(0.2)
    
    
    
    #group:进程组,目前只使用None,一般不需要设置
    #target:进程执行的目标任务
    # name:进程名,如果不设置默认是Process-1
    
    if __name__ == '__main__':
        dance_process = multiprocessing.Process(target=dance,name='dance_process')
        dance_process.start()
        #获取进程名
        print('dance_process',dance_process)
        sing_process=multiprocessing.Process(target=sing)
        sing_process.start()
    
    
        # 获取当前进程编号(主线程)
        main_process_id = os.getpid()
        print('main_process_id:',main_process_id,multiprocessing.current_process)
    
  • 执行带有参数的进程

    import multiprocessing
    
    #显示信息的任务
    def show_info(name,age):
        print(name,age)
    
    
    
    if __name__ == '__main__':
        # 创建子进程1 以元组方式传参
        sub_process = multiprocessing.Process(target=show_info, args=('张三', 18))
        sub_process.start()
        # 创建子进程2 以字典方式传参
        sub_process = multiprocessing.Process(target=show_info, kwargs={"name":"李四","age":18})
        sub_process.start()
    
  • 程序:没有执⾏的代码,是⼀个静态的

  • 线程和进程之间的对⽐

    进程:能够完成多任务,⼀台电脑上可以同时运⾏多个QQ

    线程:能够完成多任务,⼀个QQ中的多个聊天窗⼝根本区别:进程是操作系统资源分配的基本单位,⽽线程是任务调度和执⾏的基本单位

  • 进程之间的通信

    Queue-队列 先进先出

    共享全局变量不适⽤于多进程编程

  • 进程池之间的通信

    当需要创建的⼦进程数量不多时,可以直接利⽤multiprocessing中的Process

    动态⽣成多个进程,但是如果是上百甚⾄上千个⽬标,⼿动的去创建的进程的

    ⼯作量巨⼤,此时就可以⽤到multiprocessing模块提供的Pool⽅法

from multiprocessing import Pool

import os, time, random


def worker(msg):
    t_start = time.time()
    print('%s开始执行,进程号为%d' % (msg, os.getpid()))

    time.sleep(random.random() * 2)
    t_stop = time.time()
    print(msg, "执行完成,耗时%0.2f" % (t_stop - t_start))

def demo():
    pass
if __name__ == '__main__':
    po = Pool(3)  # 定义一个进程池
    for i in range(0, 10):
        po.apply_async(worker, (i,))

    print("--start--")
    po.close()
    po.apply_async(demo)
    po.join()
    print("--end--")
import multiprocessing


def demo1(q):
    print(1)
    q.put('a')

def demo2(q):
    print(2)
    print(q.get())




if __name__ == '__main__':

    # q = multiprocessing.Queue()
    q = multiprocessing.Manager().Queue() # 进程池之间进程间的通信
    po = multiprocessing.Pool(2)


    po.apply_async(demo1,args=(q,))
    po.apply_async(demo2,args=(q,))


    po.close()

    po.join()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值