吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 5409|回复: 42
收起左侧

[学习记录] 【笔记】python自学笔记(爬虫篇)——多线程

  [复制链接]
qianshang666 发表于 2021-2-22 22:27
一.前言
今天说一下多线程,可能前面的概念理解起来有点费劲,但是还是要说一下的,我们也不能对多线程一点点了解都没有,当然了,我相信到后面的实操,大家就很容易理解了

二.什么是线程
线程(thread)也叫轻量级进程,是操作系统能够进行运算调度的最小单位,它被包涵在进程之中,是进程中的实际运作单位,线程自己不拥有线程资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源,一个线程可以创建和撤销另一个线程,同一进程中的多个多线程之间可以并发执行。


三.为什么要使用多线程
线程在程序中是独立的,并发的执行流,与相互分隔的进程相比,进程中线程之间的隔离程度更小,它们共享内存,文件句柄和其他进程中应有的状态,线程共享的环境包括进程代码段,进程的共有数据等,利用这些共享的数据,线程之间很容易实现通信
总结起来,使用多线程编程具有如下几个优点:
1.进程之间不能共享内存,但是线程之间共享内存非常容易
2.操作系统在创建进程是,需要为该进程重新分配系统资源,但创建线程的代价则小得多,因此,使用多线程来实现多任务并发执行比使用多进程的效率高
3.python语言内置了多线程支持功能,而不是单纯地作为底层操作系统的调度方式,从而简化了python的多线程编程

四.线程实现
1.threading模块
[Python] 纯文本查看 复制代码
import threading    #这是python中的多线程库
import time    #时间库

def example(name1):     #函数中的两个参数分别要对应下面args中的参数,数量不同会报错
    print("线程正在运行",name1)
    time.sleep(1)     #沉睡一秒
    print("线程运行结束",name1)

if  __name__ == "__main__":    #程序的入口,类似C语言中的main()
    
    #第一个参数target是线程函数变量
    #第二个参数args是一个数组变量参数
    #如果只传递一个值,就只需要一个值,
    #如果需要传递多个参数,那么还可以继续传递下去其他的参数,
    #其中的逗号不能少,元组中只包含一个元素时,需要在元素后面添加逗号
    t1 = threading.Thread(target=example,args = ("t1",))    
    t2 = threading.Thread(target=example,args = ("t2",))  
    
    
    #上面两句是声明,我们要用start方法启动
    t1.start()
    t2.start()

线程调度.png
这是运行结果,但是有一个点,我们线程运行是t1,t2顺序,但是运行结束会出现t2,t1的顺序,原因是我们线程调度是由操作系统层面来进行操作的,它的资源分配是由OS来执行的,实际上它的底层是一种GIL锁资源抢占的方式,关于GIL我也介绍一下吧
GIL锁.png
上图中表示的是两个线程在双核CPU上得执行情况。两个线程均为CPU密集型运算线程。绿色部分表示该线程在运行,且在执行有用的计算,红色部分为线程被调度唤醒,但是无法获取GIL导致无法进行有效运算等待的时间,它们不能同时运行,所以是一种资源抢占的方式,谁先结束,谁就能先抢到,这个一定程度上无法进行人为的控制,当有至少一个CPU密集型线程存在,那么多线程效率会由于GIL而大幅下降
2.自定义线程
继承threading.Thread来自定义线程类,其本质是重构Thread类中的run方法
[Python] 纯文本查看 复制代码
import threading
import time
class MyThread(threading.Thread):
    def __init__(self,n):    #重写初始化方法(构造方法)
        super(MyThread,self).__init__()   #重构run函数必须要写
        self.n = n
    
    def run(self):    #重写run方法(调度方法)
        print("task run:",self.n)
        time.sleep(1)
        print("task finish:",self.n)

if __name__ == "__main__":
    t1 = MyThread(n="t1")
    t2 = MyThread(n="t2")
    t1.start()
    t2.start()

这个自定义线程我们一般用不到,除非你是需要在人家的基础上多增加一些实现的方法才用它
3.守护线程:我们看下面的这个例子,使用setDaemon(Ture)把所有的子线程都变成了主线程的守护线程,因此当主线程结束后,子线程也随之结束,所以当主线程结束后,整个程序就退出了。
[Python] 纯文本查看 复制代码
import threading
import time
class MyThread(threading.Thread):
    def __init__(self,n):    #重写初始化方法(构造方法)
        super(MyThread,self).__init__()   #重构run函数必须要写
        self.n = n
    
    def run(self):    #重写run方法(调度方法)
        print("task run:",self.n)
        time.sleep(1)
        print("task finish:",self.n)

if __name__ == "__main__":
    t1 = MyThread(n="t1")
    t2 = MyThread(n="t2")
    t1.setDaemon(True)    #这两句就是增加守护线程
    t2.setDaemon(True)
    t1.start()
    t2.start()

运行结果是这样的
守护线程.png
结果我们发现和我们预想的不一样,只有run,却没有finish,因为设置了守护线程,所以主线程结束程序就结束了,我写一行代码大家看一下
主线程结束运行.png
大家可以发现我们没有将彻底运行结束,那我们如果要等子线程彻底运行完的话就要使用join方法,这个就是等待的意思,等待子线程全部运行结束再停止
等待运行结束.png
4.共享全局变量
[Python] 纯文本查看 复制代码
import threading
import time

g_num = 100    #定义一个值为100的全局变量

def work1():
    global g_num    #在整个程序运行期间把g_num当作常量
    for i in range(3):
        g_num = g_num + 1
    print(g_num)

def work2():
    global g_num
    print(g_num)

if __main__ == "__main__":
    t1 = threading.Thread(target=work1)
    t1.start()
    time.sleep(1)
    t2 = threading.Thread(target=work2)
    t2.start()

其实在这儿程序中,只是多了一个global,来声明一下这个g_num共享,如果没有这个global的话,程序就会错误,因为在程序觉得在子程序中找不到g_num这个变量
5.互斥锁
由于线程之间是随即调度,并且每个线程可能只执行n条数据之后,当多个线程同时修改同一条数据是可能会出现脏数据,所以,出现了线程锁,根据我的理解就是,当多个线程进行资源抢占的时候,我这个线程在修改变量的时候,其他线程都不能进行修改,也不会把锁放开,只有我把操作全部完成之后,才会把锁放开
接下来我们看两个程序,分别就是用了互斥锁和没用互斥锁,我们比较一下他们的运行结果
(1)没用互斥锁
[Python] 纯文本查看 复制代码
from threading import Thread,Lock
import os,time

def work():
    global n
    temp = n    #n赋值给temp
    time.sleep(0.1)
    n = temp - 1


if __name__ == "__main__":
    n = 100    #全局变量
    l = []
    for i in range(100):   #100个线程运行work函数
        p = Thread(target = work)
        l.append(p)
        p.start()
    for p in l:
        p.join()    #等待每个线程都运行完毕
print(n)

(2)用了互斥锁
[Python] 纯文本查看 复制代码
from threading import Thread,Lock
import os,time

def work():
    global n
    lock.acquire()    #上锁
    temp = n    #n赋值给temp
    time.sleep(0.1)
    n = temp - 1
    lock.release()    #释放锁


if __name__ == "__main__":
    lock = Lock()   #锁机制
    n = 100    #全局变量
    l = []
    for i in range(100):   #100个线程运行work函数
        p = Thread(target = work)
        l.append(p)
        p.start()
    for p in l:
        p.join()    #等待每个线程都运行完毕
print(n)

两个程序的运行结果:
互斥锁.png
没上锁的运行结果是99,但上了锁的运行结果却是0,很奇怪对不对,其实我们稍微一分析,就能知道,最终我们想要的结果应该是0,是99的原因是出现了线程安全的问题
6.递归锁
其实和互斥锁一样的,只不过递归锁支持嵌套使用,我没用过,估计大家用到的可能性也不大,这里我就不介绍了
7.信号量
也就是我们通常说的几线程运行,我们设置最多允许几个线程同时运行,只有之前的运行完,把信号量交换回来,它才会继续运行
[Python] 纯文本查看 复制代码
import threading
import time


def run(n,semaphore):
    semaphore.acquire()
    time.sleep(1)
    print(f"run the thread:{n}")
    semaphore.release()

if __name__ == "__main__":
    num = 0
    semaphore = threading.BoundedSemaphore(5)   #最多同时允许三个线程同时运行
    for i in range(22):
        t = threading.Thread(target=run,args =("t-%s"%i,semaphore))
        t.start()
    
    while threading.active_count()!=1:
        pass
    else:
        print("-----all threads done-----")

这个程序我们总共线程是21,但是最多同时运行的线程数只有5,因为我们设置的信号量是5,当然,这个信号量你也可以自己指定

后言:其实上面说了这么多,大家一般使用的话只需要掌握普通的创建方法和互斥锁即可,当然了,还有一部分我没有说,比如事件等等,但我上面说的这些已经足够日常使用了,好的,感谢大家的观看

免费评分

参与人数 15吾爱币 +11 热心值 +13 收起 理由
xh44113 + 1 用心讨论,共获提升!
禾水木 + 1 + 1 热心回复!
yueqianxiaoyan + 1 用心讨论,共获提升!
王星星 + 2 + 1 谢谢@Thanks!
yang19950324 + 1 + 1 用心讨论,共获提升!
忆白学渣 + 1 用心讨论,共获提升!
久住飞鸟 + 1 + 1 我很赞同!
zhhmok + 1 我很赞同!
miqi1314 + 1 + 1 用心讨论,共获提升!
sdaza + 1 谢谢@Thanks!
leverager + 1 热心回复!
chinawolf2000 + 1 + 1 热心回复!
hshcompass + 1 + 1 热心回复!
黄焖柠檬 + 1 我很赞同!
Mariolll + 1 + 1 感谢作者的文章 很有用

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

lms1206 发表于 2021-2-22 22:56
好!从明天起开始学习
smibed 发表于 2021-2-22 23:06
AsuraSong 发表于 2021-2-22 23:07
hshcompass 发表于 2021-2-22 23:20
可否提供一个简单例子,谢谢分享。
304775988 发表于 2021-2-22 23:26
有错别字,但不碍事,看了楼主的主题,对多线程又多了一些理论上的了解,比较用心的总结
lsy832 发表于 2021-2-22 23:30
先收藏  慢慢学
 楼主| qianshang666 发表于 2021-2-22 23:54
304775988 发表于 2021-2-22 23:26
有错别字,但不碍事,看了楼主的主题,对多线程又多了一些理论上的了解,比较用心的总结

纯手打,有错别字也正常,
 楼主| qianshang666 发表于 2021-2-22 23:56
hshcompass 发表于 2021-2-22 23:20
可否提供一个简单例子,谢谢分享。

上面的源码就是例子呀
 楼主| qianshang666 发表于 2021-2-22 23:57
lms1206 发表于 2021-2-22 22:56
好!从明天起开始学习

那你一定要开始哦,加油
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-25 05:43

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表