• python学习笔记 day38 线程


    1. 线程 

    线程是CPU调度的最小单位,进程是CPU分配资源的最小单位;

    每个进程中至少有一个线程;(所以进程是包含线程的,同一进程的不同线程之间数据是共享的);

    开启进程的时间要比开启线程的时间长,CPU在进程之间的切换比在线程之间的切换要慢很多;

    如果有两个任务需要共享内存,有需要实现异步,就需要开启多线程;

    如果两个任务需要数据隔离--就需要开启多进程;

    多线程的优点:

    1. 轻量级;

    2. 同一个进程的多个线程之间的数据是共享的;

    进程与线程之间的区别:

    1. 地址空间和其他资源(如打开文件):进程间相互独立,同一进程的多线程之间共享数据资源,某进程内的线程在其他进程不可见;

    2. 通信:进程间通信IPC(比如队列,管道),线程之间可以直接读写线程数据段(如全局变量)来进行通信;

    3. 调度和切换:线程上下文切换,比进程上下文切换要快得多;

    4. 在多线程操作系统中,进程不是一个可执行的实体;(因为线程是CPU调度任务的最小单位,进程只是CPU资源分配的最小单位)

    2. GIL--全局解释器锁

    其实python在多线程上并没有实现真正的多并发,python的解释器加了一把锁,使得同一时间只有一个线程被CPU执行(实际上是为了保证数据的安全);

    如果是在高计算(不涉及IO操作,阻塞的)可以开多进程来实现;

    如果是在高IO(input print 文件的读写,网络通信accept recv等)可以开多线程(比如在网络爬虫,其实IO操作等待的时间是很长的,即使真的有多线程并发,也是需要等的,所以这种情况下多线程并发执行并不会带来效率的提升)

     3. 简单的开启多线程

    # 效果就是先打印主线程的进程id然后睡一秒之后同时打印10个线程的执行结果,可以看出10个线程之间是异步的,但是其实并不是真正的高并发;
    # 而且如果子线程中执行代码不睡一秒,那么打印顺序是先打印10个线程的执行结果,然后才打印主线程,就是因为开启线程的时间太短了
    from threading import Thread
    import time
    import os
    def func(i):
        time.sleep(1)   # 如果子线程这里不睡一秒,那么就是先打印子线程执行结果,然后才打印主线程的,因为开启线程的时间真的很短,还没执行到主线程的打印,开启子线程 就已经出结果了
        print("%s-hello,xuanxuan-%s"%(i,os.getpid()))
    
    
    # 开多线程时不用写在if __name__=="__main__":中
    for i in range(10):
        t=Thread(target=func,args=(i,))   # 创建10个线程对象
        t.start()  # 开启线程,子线程是异步并发的*(但是python的多线程并不是真正意义上的并发,因为有GIL全局解释器锁)
    print(os.getpid())  # 其实主线程跟开的10个子线程之间也是异步的

    运行结果:


     如果是想让主线程和多个子线程之间同步(也就是想先让多个子线程执行完毕,然后在继续执行主线程的代码):可以使用join()方法,但是注意要把开的多个线程放在一个列表中,最后统一使用join()方法关闭(这样才能实现多个线程之间的异步并发效果)否则开启一个线程就join()一下 这样多个线程之间就变为同步执行;

    from threading import Thread
    import time
    import os
    def func(i):
        time.sleep(1)   # 如果子线程这里不睡一秒,那么就是先打印子线程执行结果,然后才打印主线程的,因为开启线程的时间真的很短,还没执行到主线程的打印,开启子线程 就已经出结果了
        print("%s-hello,xuanxuan-%s"%(i,os.getpid()))
    
    
    # 开多线程时不用写在if __name__=="__main__":中
    t_lst=[]   # 存放开启的多个线程
    for i in range(10):
        t=Thread(target=func,args=(i,))   # 创建10个线程对象
        t.start()  # 开启线程,子线程是异步并发的*(但是python的多线程并不是真正意义上的并发,因为有GIL全局解释器锁)
        t_lst.append(t)
    [t.join() for t in t_lst]  # 最后统一关闭多个线程,仍然保证多个线程之间异步并发
    print(os.getpid())  # 使用join()方法之后,主线程和多个子线程之间就变为同步,就是主线程得先等待子线程执行完毕,然后才执行主线程中的代码

    运行结果:

     4. 开启线程的另一种方式----使用类

    from threading import Thread
    import os
    class MyThread(Thread):   # 自定义的类必须继承自Thread类
        def run(self):  # 必须在类内实现run()方法,线程创建之后 一旦执行t.start() 实质上就是执行类中的run()方法
            print("hello,xuanxuan-%s"%os.getpid())
    
    for i in range(10):
        t=MyThread()   # 创建10个线程
        t.start()
    print("主线程:%s"%os.getpid())

    运行结果:


     1. 如果在主线程中开了多个子线程,想统计开了多少个子线程:

    from threading import Thread
    import os
    
    class MyThread(Thread):
        _count=0   # 在类内创建一个静态属性(类在定义时就会被执行,相当于初始化,调用时不会再被执行了)---这个属性在所有的线程中共享的,因为线程之间本来就是共享数据的
        def run(self):  # 每当一个线程开启时 t.start()方法实际上会去执行类中的run()方法
            MyThread._count+=1   # 每次开启一个线程都会执行run()方法,也就是类的静态属性_count自加1
            print("hello,xuanxuan-%s"%os.getpid())
    
    for i in range(10):
        t=MyThread()  # 创建10个线程(虽然这里已经知道是创建了10个线程,但是就是想使用类的静态属性,在run()方法中操作,获取线程的个数)
        t.start()     # 开启线程,会自动执行类MyThread类的run()方法,也就是每开一个线程都会使得MyThread的_count加1
    print("主线程:%s"%os.getpid())
    print("主线程中开启的子线程数目:",t._count)  # 拿着进程对象取类的静态属性_count的值(其实所有线程的_count都是10)

     运行结果:

     2. 如果想子线程执行的函数,需要参数,应该在类中怎么实现:(在类中继承父类的__init__()方法:super().__init__(),然后再写派生属性)

    from threading import Thread
    import os
    import time
    import random
    
    class MyThread(Thread):
        _count=0   # 在类内创建一个静态属性(类在定义时就会被执行,相当于初始化,调用时不会再被执行了)---这个属性在所有的线程中共享的,因为线程之间本来就是共享数据的
        def __init__(self,arg1,arg2):   # 子线程执行函数run()时,如果想传一个参数:
            super().__init__()  # 不能重写__init__()方法,必须继承自父类Thread的__init__() 然后再写派生方法
            self.arg1=arg1
            self.arg2=arg2
        def run(self):  # 每当一个线程开启时 t.start()方法实际上会去执行类中的run()方法
            MyThread._count+=1   # 每次开启一个线程都会执行run()方法,也就是类的静态属性_count自加1
            time.sleep(random.random())
            print("%s-%s-%s"%(os.getpid(),self.arg1,self.arg2))
    t_lst=[]   # 想把开启的子线程都放在列表中,然后统一t.join()是为了保证开启的多个子线程仍然是异步的
               # 只不过是想在子线程执行完毕之后,执行主线程的代码(只是主线程和多个子线程之间同步,多个子线程之间仍然是异步并发的)
    for i in range(10):
        t=MyThread(i,i*"*")  # 创建10个线程(可以传参的)(虽然这里已经知道是创建了10个线程,但是就是想使用类的静态属性,在run()方法中操作,获取线程的个数)
        t.start()     # 开启线程,会自动执行类MyThread类的run()方法,也就是每开一个线程都会使得MyThread的_count加1
        t_lst.append(t)
    [t.join() for t in t_lst]  # 主线程会等待子线程执行完毕,才会继续执行下面的代码,但是子线程之间仍然是异步并发的
    print("主线程:%s"%os.getpid())
    print("主线程中开启的子线程数目:",t._count)  # 拿着进程对象取类的静态属性_count的值(其实所有线程的_count都是10)

    运行结果:

     3. 最后介绍在第一种开启线程的方法的一些其他的方法:

    import threading
    import time
    
    def func():
        time.sleep(1)
        print("hello,xuanxuan-线程名:%s-线程id: %s"%(threading.current_thread().name,threading.current_thread().ident))
         #  threading.current_thread().name----返回当前线程名  threading.current_thread().ident----返回当前线程id
    
    for i in range(10):
        t=threading.Thread(target=func)  # 创建10个线程
        t.start()  # 开启线程
    
    print(threading.enumerate())  # 打印当前正在执行的线程列表,包括主线程和开启的所有子线程
    print("正在执行的线程数:",len(threading.enumerate()))  # 打印正在执行的线程数
    print("正在执行的线程数:",threading.active_count())   # 也是打印正在执行的线程数

     运行结果:

    4. 作业----使用多线程实现server端和多个客户端的通信

    # server.py
    from threading import Thread
    import socket
    
    sk=socket.socket()
    # sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
    sk.bind(("127.0.0.1",8080))
    sk.listen()
    
    def func(conn):
        conn.send(bytes("hello,xuanxuan".encode("utf-8")))
        ret=conn.recv(1024).decode("utf-8")
        print(ret)
        conn.close()
    
    while True:
        conn,addr=sk.accept()
        t=Thread(target=func,args=(conn,))
        t.start()
    sk.close()
    # client.py
    import socket
    sk=socket.socket()
    
    sk.connect(("127.0.0.1",8080))
    ret=sk.recv(1024).decode("utf-8")
    print(ret)
    info=input(">>>")
    sk.send(bytes(info.encode("utf-8")))
    sk.close()

    运行结果:

    talk is cheap,show me the code
  • 相关阅读:
    C# 酒鬼买酒喝,瓶盖和空瓶子可以换新的酒
    C# 图结构操作
    C# 二叉堆
    Unity 单元测试(NUnit,UnityTestTools)
    Unity PlayerPrefs类进行扩展(整个对象进行保存)
    客户端操作判断以服务器时间为准
    C# 使用枚举获取对应的数组值时
    C# 实现简单状态机(参考代码)
    叶脉图案以及藤蔓生长算法在houdini里面的实现 Leaf Venation
    steering behaviors 转向行为 集群模拟 小结
  • 原文地址:https://www.cnblogs.com/xuanxuanlove/p/9787354.html
Copyright © 2020-2023  润新知