Python:多线程入门——多路加速,了解多线程基本原理

这是崔庆才老师的《52讲轻松搞定网络爬虫》课程相关笔记。

以下的代码段运行前请先执行此代码

import threading
import time

Thread类直接创建子线程

通过构造函数,再传递此函数给 threading.Thread(target,args) 实现多线程,其中target是目标函数,args是目标函数的参数列表。

## 这里是构造函数
def target(second):
    print(f'Threading {threading.current_thread().name} is running') ### 线程的名字我们通过 threading.current_thread().name 来获取出来,
                                                                     ### 主线程的话,其值就是 MainThread,子线程的话,其值就是 Thread-*。
    print(f'Threading {threading.current_thread().name} is sleep {second}s')
    time.sleep(second)
    print(f'Threading {threading.current_thread().name} is ended')

## 主线程开始
print(f'Threading {threading.current_thread().name} is running')
for i in [1,5]:
    t = threading.Thread(target=target,args=[i])
    t.start()
    t.join()                                                         ### 这里的作用是子线程运行结束之前主线程不能提前运行结束,
                                                                     ### 主进程等待子进程运行结束后自己才可以运行结束。
                                                                     ### 主进程等待子进程结束才结束的行为叫阻塞。
print(f'Threading {threading.current_thread().name} is ended')
Threading MainThread is running
Threading Thread-6 is running
Threading Thread-6 is sleep 1s
Threading Thread-6 is ended
Threading Thread-7 is running
Threading Thread-7 is sleep 5s
Threading Thread-7 is ended
Threading MainThread is ended

守护进程

一个线程被设置为守护线程,这意味着,如果主线程结束了而该守护线程还没有运行完,那么它将会被强制结束。

设置守护进程的方法

  • 方法一:可以通过 setDaemon(True) 方法来将某个线程设置为守护线程。
  • 方法二:通过 threading.Thread (target,args,daemon=True) 来实现将某个线程设置为守护进程。
## 这里是构造函数
def target(second):
    print(f'Threading {threading.current_thread().name} is running')
    print(f'Threading {threading.current_thread().name} sleep {second}s')
    time.sleep(second)
    print(f'Threading {threading.current_thread().name} is ended')
    
# ## 方法一:
# print(f'Threading {threading.current_thread().name} is running')
# t1 = threading.Thread(target=target, args=[2])
# t1.start()
# t2 = threading.Thread(target=target, args=[5])
# t2.setDaemon(True)                           ### 将t2设置为守护进程,它会随着主进程结束而结束。
# t2.start()
# print(f'Threading {threading.current_thread().name} is ended')

## 方法二:
print(f'Threading {threading.current_thread().name} is running')
t1 = threading.Thread(target=target, args=[2])
t1.start()
t2 = threading.Thread(target=target, args=[5],daemon=True)     ### 将t2设置为守护进程,它会随着主进程结束而结束。
t2.start()
print(f'Threading {threading.current_thread().name} is ended')
Threading MainThread is running
Threading Thread-15 is running
Threading Thread-15 sleep 2s
Threading Thread-16 is running
Threading Thread-16 sleep 5s
Threading MainThread is ended
Threading Thread-15 is ended
Threading Thread-16 is ended

继承 Thread类创建子线程

MyThread是一个继承 threading 的类,线程需要实现的方法写在run方法里

## 这是个继承了Thread的类
class MyThread(threading.Thread):
    def __init__(self, second):
        threading.Thread.__init__(self)
        self.second = second
    def run(self):
        print(f'Threading {threading.current_thread().name} is running')
        print(f'Threading {threading.current_thread().name} sleep {self.second}s')
        time.sleep(self.second)
        print(f'Threading {threading.current_thread().name} is ended')

## 主线程开始
print(f'Threading {threading.current_thread().name} is running')
threads = []                    #### 用于存放MyThread的一个list
for i in [1, 5]:
    thread = MyThread(i)
    threads.append(thread)      #### list什么都可以存,包括实例化类对象
    thread.start()
for thread in threads:
    thread.join()
print(f'Threading {threading.current_thread().name} is ended')
Threading MainThread is running
Threading Thread-6 is running
Threading Thread-6 sleep 1s
Threading Thread-7 is running
Threading Thread-7 sleep 5s
Threading Thread-6 is ended
Threading Thread-7 is ended
Threading MainThread is ended

互斥锁

线程之间是可以共享资源的,但是有些共享资源不能够同时多个线程访问的,比如,打印机,打印机一时间只能有一个线程占着。**怎么样利用多个线程轮流对一个全局变量进行加法处理呢?**这个时候需要设置互斥锁才会没问题的,下面分别列举了没设置互斥锁和设置了互斥锁的结果。

### 比如在一个进程中,有一个全局变量 count 用来计数,现在我们声明多个线程,
### 每个线程运行时都给 count 加 1,让我们来看看效果如何。
### 这里设置了1000尺循环,按常理说最终结果是1000。

# count = 0

# class MyThread(threading.Thread):
#     def __init__(self):
#         threading.Thread.__init__(self)
#     def run(self):
#         global count
#         temp = count + 1
#         time.sleep(0.001)
#         count = temp

# threads = []
# for _ in range(1000):
#     thread = MyThread()
#     thread.start()
#     threads.append(thread)

# for thread in threads:
#     thread.join()
# print(f'Final count: {count}')

### 以上运行结果不可能是1000的,因为多个线程几乎是同时执行的,
### 一个时间点的 count 会被多个线程拿来加,但线程返回 count 的值时候返回的值是同一个值,
### 也就是说加了多次相等于只加一次,所以结果肯定偏小

### 此时互斥锁就派上用场了
### 互斥锁能够严格限制资源同一时间只能一个线程访问。
### 当那个线程访问完了,才能够被另一个线程访问。

count = 0

class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
    def run(self):           #### 在 run方法里面,获取 count 前先加锁,修改完 count 之后再释放锁,
                             #### 这样多个线程就不会同时获取和修改 count 的值了
        global count
        lock.acquire()       #### 关锁,公共资源禁止其他线程访问。
        temp = count + 1
        time.sleep(0.001)
        count = temp
        lock.release()       #### 开锁,刚完事后,公共资源允许其他人访问。

lock = threading.Lock()      #### 这里声明了一个 lock 对象,其实就是 threading.Lock 的一个实例
threads = []
for _ in range(1000):
    thread = MyThread()
    thread.start()
    threads.append(thread)
for thread in threads:
    thread.join()
print(f'Final count: {count}')

Python 多线程的问题

由于 Python中 GIL的限制,导致不论是在单核还是多核条件下,在同一时刻只能运行一个线程,导致 Python多线程无法发挥多核并行的优势。GIL全称为 GlobalInterpreter Lock,中文翻译为全局解释器锁,其最初设计是出于数据安全而考虑的。

在 Python多线程下,每个线程的执行方式如下:

  • 获取 GIL 执行对应线程的代码
  • 释放 GIL

可见,某个线程想要执行,必须先拿到 GIL,我们可以把 GIL看作是通行证,并且在一个 Python进程中,GIL只有一个。拿不到通行证的线程,就不允许执行。这样就会导致,即使是多核条件下,一个 Python 进程下的多个线程,同一时刻也只能执行一个线程。

不过对于爬虫这种 IO 密集型任务来说,这个问题影响并不大。而对于计算密集型任务来说,由于 GIL的存在,多线程总体的运行效率相比可能反而比单线程更低。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值