• Python多线程/单线程


    定义:
    • 进程:资源的集合,一个程序就是一个进程。
    • 线程:一个程序最小的运行单位。
    import threading #引入线程模块
    import time
    def run():
        time.sleep(5)
        print('over')
    
    start_time=time.time()
    
    run()
    run()
    run()
    run()
    
    end_time=time.time()
    print('run_time', end_time-start_time)
    
    #结果:run_time=20.38954234234
    上面这个例子是单线程执行的,运行时间是20多秒,如果使用多线程,则会并行执行,执行结果应该是5秒左右。

    • 主线程等待子线程 

    方法一:想要让主线程等待添加的线程,需要先把创建的线程统一放到list里面,循环执行完,使用.join()方法,如下:

    import threading
    import time
    def run():
        time.sleep(5)
        print('over')
    
    start_time=time.time()
    thread_list = []
    for i in range(5):
        t=threading.Thread(target=run)  #实例化一个线程,target代表要指定执行什么,例如:target=run就是执行run()这个函数,指定函数名,不要加()
        #函数里面如果有参数,可以加上args,如:t=threading.Thread(target=run,args[1,2,3,]) ,有几个参数,就在[]里面写几个参数
        t.start() #启动这个线程
        thread_list.append(t) #把线程放到list里面
    
    for thread in thread_list:
        thread.join() #主线程等待子线程
    end_time=time.time()
    print('run_time=', end_time-start_time) 

    方法二:每一次循环都会判断下剩余的线程是不是只剩下1个,不是一个的话,继续循环,直到剩下的线程数为1时,继续往下执行,如下:

    import threading
    import time
    
    threads = []
    start_time2 = time.time()
    def insert_db():
        time.sleep(3)
        print('insert_db over')
    for i in range(3):
        t = threading.Thread(target=insert_db)
        t.start()
    while threading.activeCount()!=1:
    #每一次循环都会判断下剩余的线程是不是只剩下1个,不是一个的话,继续循环,直到剩下的线程数为1时,继续往下执行
        pass
    end_time2 = time.time()
    print('多线程执行的时间',end_time2 - start_time2)
    print('锁门...')
    • 线程 单个启动 & 同时启动

    import  threading
    
    '''单个启动'''
    for i in range(10):
        t = threading.Thread(target=func,args=[1])
        t.start()#start()在这里是一个一个线程拿过来,单个启动
    
    '''同时启动启动'''
    ts = []
    for i in range(10):
        t = threading.Thread(target=func,args=[1])
        ts.append(t)#先10个线程放到一个list里面
    for t in ts:
        t.start() #循环同时启动

    练习分析:

    import threading
    import time
    def insert_db():
        time.sleep(3)
        print('insert_db over')
    start_time = time.time()
    for i in range(3):
        t = threading.Thread(target=insert_db)
        t.start()
    end_time = time.time()
    print('多线程执行的时间',end_time - start_time)
    
    结果:
    多线程执行的时间 0.0010001659393310547
    insert_db over
    insert_db over
    insert_db over

    我们看到的结果是中运行时间是0.0010001659393310547,还不到一秒钟,而我们代码所设置的是最少也会需要3秒钟,为什么时间会是这么短呢?

    ——因为所得到的时间只是主线程执行完它的工作时间,并不包括子线程执行的额时间。 

    压测练习:

    import threading
    import requests
    def request():
        while True:
            r = requests.get('http://api.nnzhp.cn/api/user/stu_info?stu_name=%E7%8E%8B%E5%B0%8F%E6%9C%88')
            print(r.json())
    
    for i in range(10):
        t = threading.Thread(target=request)
        t.start()

    运行后会一直死循环,一直压测,可以加入压测的时间

    单线程下载网页:

    import requests,time,threading
    from hashlib import md5
    result_list = {}
    def down_load_pic(url):
        req = requests.get(url)
        m = md5(url.encode()) #把url转换为二进制md5下
        file_name = m.hexdigest()+'.png' #拼接文件名
        with open(file_name ,'wb') as fw:
            fw.write(req.content)
        # return file_name
        result_list[file_name] = threading.current_thread()
    
    url_list = ['http://www.nnzhp.cn/wp-content/uploads/2019/10/f410afea8b23fa401505a1449a41a133.png',
                'http://www.nnzhp.cn/wp-content/uploads/2019/11/481b5135e75c764b32b224c5650a8df5.png',
                'http://www.nnzhp.cn/wp-content/uploads/2019/11/b23755cdea210cfec903333c5cce6895.png',
                'http://www.nnzhp.cn/wp-content/uploads/2019/11/542824dde1dbd29ec61ad5ea867ef245.png']
    
    start_time = time.time()
    for url in url_list:
        down_load_pic(url)
    end_time = time.time()
    
    print(end_time - start_time)
    
    #结果:4.439253807067871

    多线程下载网页:

    import threading
    import requests
    import hashlib
    import time
    def down_load(url):
        name = hashlib.md5(url.encode()).hexdigest() #把url使用md5转化为密文
        r = requests.get(url)
        with open('%s.jpg'%name,'wb') as fw:
            fw.write(r.content)
    
    l = [
        'http://www.nnzhp.cn/wp-content/themes/QQ/images/logo.jpg',
        'http://www.nnzhp.cn/wp-content/uploads/2016/12/2016aj5kn45fjk5-150x150.jpg',
        'http://www.nnzhp.cn/wp-content/themes/QQ/images/thumbnail.png'
    ]
    
    for i in l:
        t = threading.Thread(target=down_load,args=[i])
        #参数只有一个时,要使用()的方式来写,需要多加一个逗号,如:args=(i,)
        # args是存参数的,如果里面只有一个参数的话,一定要在这个参数后面加一个逗号,因为是保存在元组里,如果不加逗号,它会默认为是字符串   应该写成:args=(url,)
        t.start()
    
    while threading.activeCount()!=1:
        pass
    print('down load over...'
    一个进程里面至少有一个线程,这个线程就是主线程。
    主线程只是调度用的,它把子线程招来之后就完事了,因此如果要统计运行时间,必须要让主线程等待所有的子线程都执行完后再记录结束时间。
    case_result = []
    
    def run_case(case_name):
        print('run case..',case_name)
        case_result.append(  {case_name:'success'})
    #多线程运行函数时,函数的返回值是拿不到的,所以定义一个list
    #把函数运行的结果都存进去就ok了 
    • 查看当前有多少个线程:threading.activeCount()

    import threading #引入线程模块
    import time
    def run():
        time.sleep(1)
    
    for i in range(10):
        t = threading.Thread(target=run)
        t.start()
    
    print('当前有%s个线程'%threading.activeCount()) #当前有几个线程
    
    结果:当前有11个线程
    为什么会是11个线程而不是10个线程呢?我们明明是循环启动了10个线程,结果怎么就是11个了呢?
    因为本身有一个主线程在运行,好比是我们自己在干活,我们又找了10个人来一起干活,加上我们自己一共是11个人在干活。所有的程序默认就会有一个线程。

    2、守护线程

    • 一旦主线程死掉,那么守护线程不管有没执行完事,全部结束,相当于你是一个国王(主线程),你有很多仆人(守护线程),仆人都是为你服务的,一旦你死了,那么你的仆人都需要给你陪葬。
    import threading
    import time
    def talk(name):
        print('正在和%s聊天'%name)
        time.sleep(200)
    
    print("qq主窗口")
    t = threading.Thread(target=talk,args=['刘一'])
    t.setDaemon(True) #设置线程为守护线程
    t.start()
    
    time.sleep(5)
    
    print('结束。。。')
    
    结果:
    qq主窗口
    正在和刘一聊天
    结束。。。

    如果是没有设置为守护线程,本身程序执行需要有200秒才可以结束,设置了守护线程后,主线程5秒到了就会结束,主线程结束了,剩下的正在执行的线程已经是守护线程了,不会继续200秒结束,会立即跟随主线程结束。 

    3、线程锁

    • 多个线程同时在操作同一个数据的时候,会有问题,就要把这个数据加个锁,然后同一时间只能有一个线程操作这个数据了【多个人或是多线程在同时操作同一个数据时,可能会有问题,可以加下锁】

    • 忘记了解锁或是代码运行时没有处理异常而没有走到解锁那里,都会成为线程死锁。

    举例理解:比如说我们家里的卫生间,男女共用的,如果你进去时,没有锁门,有可能会有其他的开门,所以你需要进去时把门锁一下,不用了,再把锁打开下。

    import threading
            from threading import Lock
            num = 0
            lock = Lock()#申请一把锁
            def run():
                global num
                lock.acquire(timeout = 3)#加锁,timeout是指定下时间,可以不写,如果指定了时间,超过后申请的锁就会失效
                num+=1
                lock.release()#解锁
    
            lis = []
            for i in range(5):
                t = threading.Thread(target=run)
                t.start()
                lis.append(t)
            for t in lis:
                t.join()
            print('over',num)
    #多个线程操作同一个数据的时候,就得加锁
    import threading
    
    num = 0
    lock = threading.Lock() #申请一把锁,实例化一把锁
    
    def add():
        global num
        # lock.acquire()#加锁
        # num+=1
        # lock.release()#解锁  #不解锁,就会产生线程死锁,会一直在等待
        with lock:#简写,用with也会帮你加锁,解锁
            num+=1
    
    for i in range(20):
        t = threading.Thread(target=add,)
        t.start()
    
    while threading.activeCount() !=1:
        pass
    •  查看当前哪个线程在执行:threading.current_thread()

    import threading
    count = 0
    lock = threading.Lock()
    def test():
        global count
        print(threading.current_thread())
        lock.acquire()#加锁
        count+=1
        lock.release()#解锁
        #线程死锁
    
    for i in  range(3):
        t = threading.Thread(target=test)
        t.start()
    
    结果:
    <Thread(Thread-1, started 9000)>
    <Thread(Thread-2, started 4820)>
    <Thread(Thread-3, started 10224)>

    下面来个简单的爬虫,看下多线程的效果:

    import threading
    import requests, time
    
    urls = {
        "baidu": 'http://www.baidu.com',
        "blog": 'http://www.nnzhp.cn',
        "besttest": 'http://www.besttest.cn',
        "taobao": "http://www.taobao.com",
        "jd": "http://www.jd.com",
    }
    
    def run(name, url):
        res = requests.get(url)
        with open(name + '.html', 'w', encoding=res.encoding) as fw:
            fw.write(res.text)
    
    start_time = time.time()
    lis = []
    for url in urls:
        t = threading.Thread(target=run, args=(url, urls[url]))
        t.start()
        lis.append(t)
    for t in lis:
        t.join()
    end_time = time.time()
    print('run time is %s' % (end_time - start_time))
    
    #下面是单线程的执行时间
            # start_time = time.time()
            # for url in urls:
            #     run(url,urls[url])
            # end_time = time.time()
            # print('run time is %s'%(end_time-start_time))

    4、多进程,上面说了Python里面的多线程,是不能利用多核CPU的,如果想利用多核CPU的话,就得使用多进程,python中多进程使用multiprocessing模块。

    • 一个电脑有几核的CPU,就只能同时运行几个任务

           ——比如说我们的电脑是4核的CPU,只可以同时运行4个进程,但我们在实际使用中,如果是4核的CPU,运行的并不止是4个程序,这是因为CPU上下文切换的,这个程序运行完了, 

                  会立即切换到另外一个运行,我们感觉不到。

    import multiprocessing,time
    
    lock = multiprocessing.Lock() #申请一把锁
    a = 1
    
    def test():
        pass
    
    def down_load():
        for i in range(5):
            t = threading.Thread(target = test)#启动多进程的同时,可以在启动多个多线程
            time.sleep(3)
            global a
            with lock: #使用with这个方法会自动的去判断使用完了自动的关掉锁释放【with是自动管理上下文的,操作文件时也可以使用,会自动识别关闭】
                a+=1
            print("运行完了")
    
    if __name__ == '__main__':
        for i in range(5):
            p = multiprocessing.Process(target=down_load)
            # p=multiprocessing.Process(target=down_load,args =[1,2],name='brf') #有参数了可以加上args,可以定义进程名字
            p.start()
            # print(p.pid()) 查看进程ID
    
        while len(multiprocessing.active_children())!=0:#等待子进程结束,【multiprocessing.active_children()】是查看这个进程里面存活着几个子线程
            pass
                   
    print(multiprocessing.current_process()) 
    print('end')

    5、进程池

    还可以使用进程池来快速启动几个进程,使用进程池的好处的就是他会自动管理进程数,咱们只需要给他设置一个最大的数就ok了。有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。【数据大时使用】

    from multiprocessing import Pool
    import os
     
     
    def worker(msg):
        print("%s开始执行,进程号为%d" %  (msg,os.getpid()))
     
    if __name__ == '__main__':
        
        po = Pool(3)  # 定义一个进程池,最大进程数3
        for i in range(0, 10):
            # Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
            # 每次循环将会用空闲出来的子进程去调用目标
            po.apply_async(func=worker,args=(i,))
            #第一个func参数指定运行的函数,第二个args是参数,没有参数可以不写
        print("----start----")
        po.close()  # 关闭进程池,关闭后po不再接收新的请求
        po.join()  # 等待po中所有子进程执行完成,必须放在close语句之后
        print("-----end-----")

     总结:

    • 线程是用来干活的,只有进程的话是没办法运行的,进程里其实是线程在具体干活的。

    • 线程和线程之间是互相独立的。

    • 线程是在进程里面

    • 默认,一个进程里面只有一个进程

    • 进程相当于是一个工厂,线程相当于是工厂里面具体干活的一个人

    • 主线程,也就是程序一开始运行的时候,最初的那个线程

    • 子线程,通过thread类实例化的线程,都是子线程

    • 主线程等待子线程,执行结束后,主线程再去做别的操作

    • 主线程执行完它自己工作的时间,并不包括子线程执行的时间

    • Python里面的多线程利用不了多核的cpu,因为如果是在多个CPU上运行,运行的结果会不太一样,所以加入一个全局解释器锁(GLI),保证线程都在同一个cpu运行
    • 多进程可以利用多核CPU
    • CPU密集型任务,用多进程  ->消耗的CPU比较多
    • IO(磁盘IO、网络IO)密集型任务,用多线程   ->消耗IO比较多【IO是上传、下载的意思】:磁盘IO、网络IO
    • 多线程,线程之间的的数据是共享的,每个线程都可以操作里面的数据
    • 多进程,每个进程之间的数据是独立的,正是因为每个进程都是独立的,所以多进程里面加锁是没任何意义的
    • GLI 全局解释器所--->保证线程都在同一个cpu上运行
    • 【协程】:一个线程,速度很快,从头到尾只有一个线程在运行,不会再起线程;原理:只有一个线程,不再起线程,不去多浪费资源,任务来了,主要利用了异步IO,一个线程来回的切换,性能很好;如:Nginx就是使用了协程

    扩展:

    协程,只有有个线程在运行,每遇到消耗IO的地方就会立马切换,在切换到另一个,等着这个IO结束了,再去拿到数据,性能比较好;如:nginx就是使用的协程。


    任何付出都是值得的,会越来越好  

  • 相关阅读:
    OSX 10.8+下开启Web 共享 的方法
    OSX 10.8+下开启Web 共享 的方法
    OSX 10.8+下开启Web 共享 的方法
    ★如何解释特修斯之船问题?
    ★如何解释特修斯之船问题?
    ★如何解释特修斯之船问题?
    JS中event.keyCode用法及keyCode对照表
    JS中event.keyCode用法及keyCode对照表
    JS中event.keyCode用法及keyCode对照表
    用webgl打造自己的3D迷宫游戏
  • 原文地址:https://www.cnblogs.com/brf-test/p/11901937.html
Copyright © 2020-2023  润新知