• 线程进程协程



    string有很多行时,只能用三引号。其他时候都可以用。

    num = [int(i) for i in inputs.split()]
    # input().split()读一行以空格分开的元素,然后用int()转为整数
    #input是字符串 有很多个数的
    
    • 1
    • 2
    • 3
    #用户类
    class User:
       def __init__(self,first_name,last_name):
           self.first_name = first_name
           self.last_name = last_name
       def describe_user(self):
            print(f"当前用户的firstname是{self.first_name},lastname是{self.last_name}")
    
    user1 = User(input("请输入firstname"),input('请输入lastname')) #函数调用时还可以直接用input
    user1.describe_user()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在一个类中调用另一个类

    class Restaurant: #类名首字母大写
        def __init__(self,restaurant_name,cuisine_type):
            self.restaurant_name = restaurant_name
            self.cuisine_type = cuisine_type
            self.number_served = 0  #就餐人数设置为0
        def people_num(self):
            print(f"当前已经有{self.number_served}个人在这里吃饭过")
        def set_number_served(self,number):
            '''修改就餐人数'''
            self.number_served = number
            self.people_num()	#在一个方法中调用另一个方法要用self.方法名()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    子类继承父类的属性

    class ElectricCar(Car):
        def __int__(self,make,model,year):
            """初始化父类的属性"""
            super().__init__(make,model,year)
    
    • 1
    • 2
    • 3
    • 4

    初始化为空字符串,在后面的时候判断是否为空(但不是None),实现该参数的可选性

    def get_formatted_place(City,Country,population=''):
        if population:
            formatted_place = f"{City} {Country}-{population}"
        else:
            formatted_name = f"{City} {Country}"
        return formatted_name
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    多线程

    线程的实现

    线程的两种实现方式

    1. 定义运行线程函数,使用线程构造函数直接实现
    import threading
    import time
    
    def loop():
        '''新的线程执行的代码'''
        n = 0
        while n < 5:
            print(n)
            now_thread = threading.current_thread()  # current_thread 用于调用当前正在运行的线程名称
            print(f"当前正在运行的线程名称:{now_thread}")
            time.sleep(1)   #休眠1s
            n += 1
    
    def use_thread():
        """ 使用线程来实现 """
        #创建实例对象
        t = threading.Thread(target=loop,name='loop_thread') #目标函数是loop()函数,线程运行的是loop()函数
        #启动线程
        t.start()
        #挂起线程
        t.join()
    
    if __name__ == '__main__':
        now_thread = threading.current_thread() #current_thread 用于调用当前正在运行的线程名称
        print(f"当前正在运行的线程名称:{now_thread}")
        use_thread()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    1. 构造继承自threading.Thread 的类,用该类实现线程的运行
    import threading
    import time
    
    class LoopThread(threading.Thread): #继承自threading.Thread的类
        n = 0
        def run(self):		#重写run方法
            while self.n < 5:
                now_thread = threading.current_thread()
                print(f"当前正在运行的线程名称:{now_thread}")
                time.sleep(1)
                self.n += 1
    
    
    if __name__ == '__main__':
        t = LoopThread(name = 'LoopThread')
        now_thread = threading.current_thread()
        print(f"当前正在运行的线程名称:{now_thread}")
        t.start() #启动进程
        t.join()  #挂起进程
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    多线程并发的问题

    import threading,time
    
    #银行存款为balance
    balance = 0
    
     def change(n):
         #现存后取,结果应该为0
         global balance  #在函数内部调用全局变量,需定义    global 变量名
         balance += n
         balance -= n
         print(f">>>>>>>>>>{n,balance}")
    
     def run_thread(n):
         for i in range(10000):
             change(n)      #线程中调用函数
    
     if __name__ == '__main__':
         t1 = threading.Thread(target=run_thread, args=(5,)) # args: 以元组的方式给任务传入参数
         #换句话说,线程t1将调用函数run_thread(5)来执行任务,将数字5作为参数传递给该函数。
         t2 = threading.Thread(target=run_thread, args=(8,)) # args: 以元组的方式给任务传入参数
         t1.start()
         t2.start()
         t1.join()
         t2.join()
         print(balance)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    
    #银行存款为balance
    balance = 0
    
    def change(n):
        global balance
        now_thread = threading.current_thread()
        print(f"当前正在运行的线程名称:{now_thread}")
        balance += n
        now_thread = threading.current_thread()
        print(f"当前正在运行的线程名称:{now_thread}")
        balance -= n
        print(f"当前存取金额:{n},当前钱数{balance}")
    
    
    class ChangeBalanceThread(threading.Thread):
        """改变银行余额的线程"""
    
        def __init__(self,num,*args,**kwargs): #num表示存入银行的钱数  *args用于收集任意数量的位置参数, **kwargs用于收集任意数量的关键字参数
            super().__init__(*args,**kwargs)
            self.num = num
    
        def run(self):
            for i in range(10000):
                change(self.num)
    
    if __name__ =='__main__':
        now_thread = threading.current_thread()
        print(f"当前正在运行的线程名称:{now_thread}")
        t1 = ChangeBalanceThread(5)  #线程1
        t2 = ChangeBalanceThread(8) #线程2
        t1.start()    #启动线程
        t2.start()    #启动线程
        t1.join()   #挂起线程
        t2.join()   #挂起线程
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    多线程中的锁

    线程的库是threading
    引入time库是为了休眠一会儿
    线程中的锁有threading.Loc() threading.RLock() threading.

    #代码是 多线程并发问题 代码的改进
    import threading,time
    
    #银行存款为balance
    balance = 0
    
    #定义锁
    my_lock = threading.Lock() 
    #RLock锁
    # my_lock = threading.RLock()
    def change(n):
         #现存后取,结果应该为0
         #方法一
        # try:
        #      my_lock.acquire()  #获取锁,即上锁
        #      global balance  #在函数内部调用全局变量,需定义    global 变量名
        #      balance += n
        #      balance -= n
        #      print(f">>>>>>>>>>{n,balance}")
        # finally:
        #     my_lock.release()   #释放锁
    
        #方法二
        with my_lock:
            global balance
            balance += n
            balance -= n
            print(f">>>>>>>{n,balance}")
             
    def run_thread(n):
        for i in range(10000):
            change(n)      #线程中调用函数
    
    if __name__ == '__main__':
         t1 = threading.Thread(target=run_thread, args=(5,)) # args: 以元组的方式给任务传入参数
         t2 = threading.Thread(target=run_thread, args=(8,)) # args: 以元组的方式给任务传入参数
         t1.start()
         t2.start()
         t1.join()
         t2.join()
         print(balance)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    线程池

    import time
    import threading
    from concurrent.futures import ThreadPoolExecutor
    from multiprocessing.dummy import Pool  		#进程中的线程池
    
    
    def run(n):
        """ 线程要做的事情 """
        time.sleep(2)
        print(threading.current_thread().name, n)
    
    
    def main():
        """ 使用传统的方法来做任务 """
        t1 = time.time()    #获取当前的时间
        for n in range(5):
            run(n)
        print(time.time() - t1)     #输出运行的时间
    
    
    def main_use_thread():
        """ 使用线程优化任务 """
        # 假设资源有限,最多只能跑10个线程:
        t1 = time.time()    #获取当前时间
        ls = []
        for count in range(10): #跑10次,一次2s,一次10个线程并发执行。
            for i in range(10):     #创建10个线程,并发执行
                t = threading.Thread(target=run,args=(i,))
                ls.append(t)
                t.start()
            for l in ls:    #10个线程执行完之后,全部阻塞
                l.join()
        print(time.time() - t1)
    
    
    def main_use_pool():
        """ 使用线程池来优化 """
        t1 = time.time()
        n_list = range(100)
        pool = Pool(10)     #创建了一个具有10个线程的线程池
        pool.map(run,n_list)    #n_list中的每个元素作为run的参数 映射关系
        pool.close()        #关闭线程池
        pool.join()         #等待所有任务执行完成后阻塞
        print(time.time() - t1)
    
    
    #用另一种线程池来实现
    def main_use_executor():
        """ 使用ThreadPoolExecutor """
        t1 = time.time()
        n_list = range(100)
        with ThreadPoolExecutor(max_workers=10) as executor:
            executor.map(run,n_list)
        print(time.time() - t1)
    
    
    if __name__ == '__main__':
        #main()
        #main_use_thread()
        #main_use_pool()
        main_use_executor()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    进程

    使用multiprocessing实现多进程代码
    使用multiprocessing.Process创建进程
    start()启动进程
    join()挂起进程
    import os os.getpid()获取进程id

    进程 的实现用函数;

    import os #用于得到进程的pid
    import time
    from multiprocessing import Process
    
    
    def do_sth(name):
        """
        :param name: str 表示进程的名称
        """
        print(f"进程的名称为{name},进程的id为{os.getpid()}") #os.getpid()可用于获取当前进程的pid
        time.sleep(2)   #休眠2s
        print("进程要做的事")
    
    
    if __name__ == '__main__':
        # 目标函数是do_sth 参数是一个元组,传入进程名称
        #下面两种方法都可以建立进程
        # p = Process(target=do_sth('My Process yellow'))
        p = Process(target=do_sth,args=('my process yellow',))
        #启动进程
        p.start()
        #挂起进程
        p.join()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    进程 的实现用重写类方法:

    import os
    import time
    from multiprocessing import  Process
    
    
    def do_sth(name):
        """
        进程要做的事情
        :param name: str 进程的名称
        """
        print(f"进程的名称{name},进程的id:{os.getpid()}")
        time.sleep(2)
        print("进程要做的事情")
    
    class MyProcess(Process):    #继承Process
    
        def __init__(self,name,*args,**kwargs):
            # #将进程的属性my_name赋值为name。不赋值为name是因为其父类Process中也有属性叫name,后面再继承父类会覆盖前面的赋值
            self.my_name = name
            #继承父类
            super().__init__(*args,**kwargs)
    
        def run(self):  #重写run函数
            print(f"当前进程的名称{self.my_name},"
                  f"进程的id:{os.getpid()}")
            time.sleep(2)   #休眠2s
            print("进程要做的事")
    
    if __name__ == '__main__':
        #创建一个使用继承类实现进程的方法
        p = MyProcess('my process using class')
        #运行进程
        p.start()
        #挂起进程
        p.join()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    进程之间的通信

    通过使用multiprocess中的Queue实现进程之间的通信。
    获取当前进程名称的方法:

    1.如果是写一个函数,直接用Process(target =,args = )则需要调用multiprocessing中的current_process.name
    2.如果是重写multiprocessing中的Process方法,则直接调用multiprocessing中的self.name即为当前进程的名称

    import multiprocessing
    import time
    from multiprocessing import Process,Queue
    from random import randint
    
    class WriteProcess(Process):
        """ 实现写进程 """
        def __init__(self,q,*args,**kwargs):
            self.q = q  #q是 队列
            super().__init__(*args,**kwargs)
    
        def run(self):
            """实现进程的业务逻辑"""
            #要写的内容
            ls = ['第一行内容',
                   '第二行内容',
                   '第三行内容']
            for line in ls:
                print(f"当前在写入:{line}")
                self.q.put(line)
    
                #如何获取当前进程对象的名称?
                #1.如果是写一个函数,直接用Process(target =,args = )则需要调用multiprocessing中的current_process.name()
                #2.如果是重写multiprocessing中的Process方法,则直接调用multiprocessing中的self.name即为当前进程的名称
                print(f"当前进程的名称{self.name}")
    
                #每写入一次,休息1-6s
                time.sleep(randint(1,6))
    
    
    class ReadProcess(Process):
        """ 实现读进程 """
        def __init__(self,q,*args,**kwargs):
            self.q = q
            super().__init__(*args,**kwargs)
    
        def run(self):
            # 用死循环来实现读进程
            while True:
                content = self.q.get()
                print(f"读取到的内容是{content}")
                # 如何获取当前进程对象的名称?
                # 1.如果是写一个函数,直接用Process(target =,args = )则需要调用multiprocessing中的current_process.name
                # 2.如果是重写multiprocessing中的Process方法,则直接调用multiprocessing中的self.name即为当前进程的名称
                print(f"当前进程的名称{multiprocessing.current_process().name}")
    
    if __name__ == '__main__':
        #创建队列对象
        q = Queue()
        #写进程
        writeprocess = WriteProcess(q)
        #读进程
        readprocess = ReadProcess(q) #读写进程采用同一个队列对象
        writeprocess.start()
        readprocess.start()
        writeprocess.join()
        #如果采用读进程阻塞,而读进程为死循环,则读进程无法结束。所以不应该阻塞读进程,应该直接终止terminate读进程
        # readprocess.join()
        readprocess.terminate() #终止读进程,此时程序可直接结束。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    进程中的锁

    锁有Lock RLock condition。
    Lock只能锁一次,RLock和Lock的唯一不同是RLock可以锁定两次
    笔记:

     #以追加a+的方式往文件中写 打开文件的编码方式为utf-8
    with open(self.file_name,'a+',encoding='utf-8') as f:   
    
    • 1
    • 2
    import random
    import time
    from multiprocessing import Process, Lock
    
    
    class WriteProcess(Process):
        """ 往文件中写入内容 """
        def __init__(self,file_name,num,lock,*args,**kwargs):
            self.file_name = file_name
            self.num = num
            #锁对象
            self.lock = lock
            super().__init__(*args,**kwargs) #*args, **kwargs 叫魔法参数
    
        def run(self):
            # 往文件中写内容
            with self.lock:	#必须是self.lock 锁是类的对象。要用self.
                for i in range(5):
                    content = f"当前进程名称是{self.name},进程pid是{self.pid}, 数字是{self.num}"
                    with open(self.file_name,'a+',encoding='utf-8') as f:    #以追加a+的方式往文件中写 打开文件的编码方式为utf-8
                        f.write(content+'\n') #写入内容
                        #time.sleep(random.randint(1,5)) #随机休息1-5秒钟
    
    if __name__ == '__main__':
        file_name = 'name.txt' 
        #锁的对象
        lock = Lock()
        for x in range(5):
            p = WriteProcess(file_name,x,lock)
            p.start()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    进程池

    import multiprocessing
    from multiprocessing import pool
    
    def run(file_name,num):
         """往文件中写入数据"""
         with open(file_name,'a+',encoding='utf-8') as f:
            now_process = multiprocessing.current_process()
            content = f"当前进程的名称是{now_process.name}," \
                      f"进程的id是{now_process.pid}," \
                      f"要写入的字符是{num}"
            f.write(content+'\n')
            return 'ok'
    
    
    if __name__ =='__main__':
        #创建一个容量为2的进程池
        pool = multiprocessing.Pool(2)
        file_name = 'tes_using_pool.txt'
        for i in range(20):
            #apply方法是同步方法
            # result = pool.apply(run,args=(file_name,i)) #等价与run(file_name,i)
           
            #使用异步方法
            result = pool.apply_async(run,args=(file_name,i))
            #使用get方法可以得到写入数据返回的结果。
            print(f"{i}----{result.get()}")
    
        #关闭池子
        pool.close()
        pool.join()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    协程(未看懂)

    协程的实现

    协程的实现的步骤:

    1. 定义协程函数 async def
    2. 创建事件的循环队列 loop = asyncio.get_event_loop()
    3. 注册任务,把任务加入到事件的循环队列中 task = loop.create_task(coroutine)
    4. 等待协程的执行结束 loop.run_until_complete(task)

    asyncio模块
    asyncio.get_event_loop() 获取事件循环队列
    loop.run_until_complete(task) 注册任务到队列
    在事件循环中调度其执行前,写成对象不执行任何操作。
    asyncio模块用于事件循环

    import asyncio
    import time
    
    
    async def do_sth(x):
        """定义协程函数"""
        print(f"等待中:{x}")
        #等待5s再往后执行
        await asyncio.sleep(x)
    
    #判断是否为协程函数
    print(asyncio.iscoroutinefunction(do_sth))
    
    coroutine = do_sth(5)
    #事件的循环队列
    loop = asyncio.get_event_loop()
    #注册任务,把任务加到事件循环队列中
    task = loop.create_task(coroutine)
    print(task)
    #等待协程任务执行结束
    loop.run_until_complete(task)
    print(task)
    
    
    True
    <Task pending name='Task-1' coro=<do_sth() running at D:\python\多线程和进程\协程的实现.py:5>>
    等待中:5
    <Task finished name='Task-1' coro=<do_sth() done, defined at D:\python\多线程和进程\协程的实现.py:5> result=None>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    协程函数的嵌套调用

    要得到一个协程函数的运算结果使用await方法
    asyncio 异步IO

    import asyncio
    
    
    async def compute(x,y):
        print(f"Compute {x} + {y}")
        await asyncio.sleep(1.0)
        return x + y
    
    async def print_sum(x,y):
        result = await compute(x,y) #使用await才能拿到另一个协程的运算结果。  
        print(f"{x} + {y} = {result}")
    
    #拿到事件循环
    loop = asyncio.get_event_loop()
    loop.run_until_complete(print_sum(1,2))
    loop.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    协程通信之队列

    import asyncio
    
    
    #使用队列q实现协程函数之间的通信
    
    #往队列中添加数据
    async def add(q,name):
    
        #使用for循环往队列中添加数据
        for i in range(5):
            content = f"当前协程名称{name},写入队列的内容是{i}"
            # asyncio中的队列添加时返回的时写成对象,因此必须在前面加await
            #q.put(i)
            await asyncio.sleep(2)  # 休息2s
            await q.put(i)  #返回的是协程的对象,因此必须await来获取结果。
            print(f"{content},当前队列中的元素个数为{q.qsize()}")
    
    async def reduce(q,name):
        """
        :param q:
        从队列中删除对象
        """
        for i in range(10):
            content = f"当前协程的名称是{name},要删除的内容是{i}"
            result = await q.get()  #获取协程对象,使用await等待协程完成
            print(f"{content},当前元素剩余为{q.qsize()}")
    
    
    if __name__ =='__main__':
        #准备一个队列
        q = asyncio.Queue(maxsize=5)
        a1 = add(q,'a1')
        a2 = add(q,'a2')
        r1 = reduce(q,'r1')
    
        #添加到事件队列
        loop = asyncio.get_event_loop()
        loop.run_until_complete(asyncio.gather(a1,a2,r1)) #将多个协程同时添加到事件队列中
        loop.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
  • 相关阅读:
    Oracle 的LogMiner
    微积分 - 对数函数与指数函数的导数
    xss的绕过
    基于回溯搜索优化的BP神经网络(分类应用) - 附代码
    [ vulhub漏洞复现篇 ] Hadoop-yarn-RPC 未授权访问漏洞复现
    Nginx解决vue项目服务器部署以及跨域访问后端
    IT冷知识--每日一练
    Orleans的成员管理和故障检测故障检测
    缓存过期都有哪些策略?
    内网渗透学习-环境搭建
  • 原文地址:https://blog.csdn.net/qq_45895217/article/details/121961569