熬了两个通宵写的!终于把多线程和多进程彻底讲明白了!

Python控制多进程与多线程并发数总结 一、前言 本来写了脚本用于暴力破解密码,可是1秒钟尝试一个密码2220000个密码我的天,想用多线程可是只会一个for全开,难道开2220000个线程吗?只好学习控制线程数了,官方文档不好看,觉得结构不够清晰,网上找很多文章也都不很清晰,只有for全开线程,没有控制线程数的具体说明,最终终于根据多篇文章和官方文档算是搞明白基础的多线程怎么实现法了,怕长时间不用又忘记,找着麻烦就贴这了,跟我一样新手... 阅读详情

我们知道,在一台计算机中,我们可以同时打开许多软件,比如同时浏览网页、听音乐、打字等等,看似非常正常。但仔细想想,为什么计算机可以做到这么多软件同时运行呢?这就涉及到计算机中的两个重要概念:多进程和多线程了。(PS:万字长文,讲得很详细,建议先收藏再好好看!)

同样,在编写爬虫程序的时候,为了提高爬取效率,我们可能想同时运行多个爬虫任务。这里同样需要涉及多进程和多线程的知识。

本文,我们就先来了解一下多线程的基本原理,以及在 Python 中如何实现多线程。

1. 全局解释器锁

全局解释器锁 (英语:Global Interpreter Lock,缩写 GIL)

是 计算机程序设计语言解释器用于同步线程的一种机制,它使得任何时刻仅有一个线程在执行,即便在多核心处理器上,使用GIL的解释器也只允许同一时间执行一个线程。常见的使用GIL的解释器有CPython与 Ruby MRI。

如果,你对上面的不理解,也没有问题。通俗的解释就是:你电脑是一核或者多核 ,还是你的代码写了多个线程,但因为GIL锁的存在你也就只能运行一个线程,无法同时运行多个线程。

接下来,我们来用个图片来解释一下:
在这里插入图片描述
比如图中,假如你开了两个线程(Py thread1 、Py tread2),

当我们线程一(Py thread1)开始执行时,这个线程会去我们的解释器中申请到一个锁。也就是我们的 GIL 锁;

然后,解释器接收到一个请求的时候呢,它就会到我们的 OS 里面,申请我们的系统线程;

系统统一你的线程执行的时候,就会在你的 CPU 上面执行。(假设你现在是四核CPU);

而我们的另一个线程二(py thread2)也在同步运行。

而线程二在向这个解释器申请 GIL 的时候线程二会卡在这里(Python 解释器),因为它的 GIL 锁已经被线程一给拿走了(也就是说:他要进去执行,必须拿到这把锁);

线程二要运行的话,就必须等我们的线程一运行完成之后(也就是把我们的 GIL 释放之后(图片中的第5步)线程二才能拿到这把锁);

当线程二拿到这把锁之后就和线程一的运行过程一样。

① Create > ② GIL > ③ 申请原生线程(OS) > ④ CPU 执行(如果有其他线程,都会卡在 Python 解释器的外边)

这个锁其实是 Python 之父想一劳永逸解决线程的安全问题(也就是禁止多线程同时运行)

2. 多线程的含义

说起多线程,就不得不先说什么是线程。然而想要弄明白什么是线程,又不得不先说什么是进程。

进程我们可以理解为是一个可以独立运行的程序单位。

比如:

打开一个浏览器,这就开启了一个浏览器进程;

打开一个文本编辑器,这就开启了一个文本编辑器进程。

但一个进程中是可以同时处理很多事情的。

比如:在浏览器中,我们可以在多个选项卡中打开多个页面。

有的页面在播放音乐,

有的页面在播放视频,

有的网页在播放动画,它们可以同时运行,互不干扰。

为什么能同时做到同时运行这么多的任务呢?

这里就需要引出线程的概念了,其实这一个个任务,实际上就对应着一个个线程的执行。

而进程呢?

它就是线程的集合,进程就是由一个或多个线程构成的,线程是操作系统进行运算调度的最小单位,是进程中的一个最小运行单元。

比如:

上面所说的浏览器进程,其中的播放音乐就是一个线程,播放视频也是一个线程,当然其中还有很多其他的线程在同时运行,这些线程的并发或并行执行最后使得整个浏览器可以同时运行这么多的任务。

了解了线程的概念,多线程就很容易理解了,多线程就是一个进程中同时执行多个线程,前面所说的浏览器的情景就是典型的多线程执行。

3. 并发和并行

说到多进程和多线程,这里就需要再讲解两个概念,那就是并发和并行。我们知道,一个程序在计算机中运行,其底层是处理器通过运行一条条的指令来实现的。

3.1 并发

英文叫作 concurrency。它是指同一时刻只能有一条指令执行,但是多个线程的对应的指令被快速轮换地执行。比如:

一个处理器,它先执行线程 A 的指令一段时间,再执行线程 B 的指令一段时间,再切回到线程 A 执行一段时间。

由于处理器执行指令的速度和切换的速度非常非常快,人完全感知不到计算机在这个过程中有多个线程切换上下文执行的操作,这就使得宏观上看起来多个线程在同时运行。但微观上只是这个处理器在连续不断地在多个线程之间切换和执行,每个线程的执行一定会占用这个处理器一个时间片段,同一时刻,其实只有一个线程在执行。

3.2 并行

英文叫作 parallel。它是指同一时刻,有多条指令在多个处理器上同时执行,并行必须要依赖于多个处理器。不论是从宏观上还是微观上,多个线程都是在同一时刻一起执行的。

并行只能在多处理器系统中存在,如果我们的计算机处理器只有一个核,那就不可能实现并行。

而并发在单处理器和多处理器系统中都是可以存在的,因为仅靠一个核,就可以实现并发。

举个例子

比如系统处理器需要同时运行多个线程。如果系统处理器只有一个核,那它只能通过并发的方式来运行这些线程。如果系统处理器有多个核,当一个核在执行一个线程时,另一个核可以执行另一个线程,这样这两个线程就实现了并行执行,当然其他的线程也可能和另外的线程处在同一个核上执行,它们之间就是并发执行。具体的执行方式,就取决于操作系统的调度了。
在这里插入图片描述
4. 多线程适用场景

在一个程序进程中,有一些操作是比较耗时或者需要等待的,比如等待数据库的查询结果的返回,等待网页结果的响应。如果使用单线程,处理器必须要等到这些操作完成之后才能继续往下执行其他操作,而这个线程在等待的过程中,处理器明显是可以来执行其他的操作的。如果使用多线程,处理器就可以在某个线程等待的时候,去执行其他的线程,从而从整体上提高执行效率。

像上述场景,线程在执行过程中很多情况下是需要等待的。

比如

网络爬虫就是一个非常典型的例子,爬虫在向服务器发起请求之后,有一段时间必须要等待服务器的响应返回,这种任务就属于 IO 密集型任务。对于这种任务,如果我们启用多线程,处理器就可以在某个线程等待的过程中去处理其他的任务,从而提高整体的爬取效率。

但并不是所有的任务都是 IO 密集型任务,还有一种任务叫作计算密集型任务,也可以称之为 CPU 密集型任务。顾名思义,就是任务的运行一直需要处理器的参与。此时如果我们开启了多线程,一个处理器从一个计算密集型任务切换到切换到另一个计算密集型任务上去,处理器依然不会停下来,始终会忙于计算,这样并不会节省总体的时间,因为需要处理的任务的计算总量是不变的。如果线程数目过多,反而还会在线程切换的过程中多耗费一些时间,整体效率会变低。

所以,如果任务不全是计算密集型任务,我们可以使用多线程来提高程序整体的执行效率。尤其对于网络爬虫这种 IO 密集型任务来说,使用多线程会大大提高程序整体的爬取效率。

5. Python 实现多线程

在 Python 中,实现多线程的模块叫作 threading,是 Python 自带的模块。下面我们来了解下使用 threading 实现多线程的方法。

在具体实现之前,我们先来测试一下多线程与当线程裸奔的速度对比,为了更加直观,我这里使用把每种线程代码单独写出来并做对比:

单线程裸奔:(这也是一个主线程(main thread))

import timedef start():
    for i in range(1000000):
        i += i    return# 不使用任何线程(裸着来)def main():
    start_time = time.time()    for i in range(10):
        start()
    print(time.time()-start_time)if __name__ == '__main__':
    main()

输出:

6.553307056427002

注意:因为每台电脑的性能不一样,所运行的结果也相对不同(请按实际情况分析)

接下来我们写一个多线程

我们先创建个字典 (thread_name_time) 来存储我们每个线程的名称与对应的时间

import threading,timedef start():
    for i in range(1000000):
        i += i    return# # 不使用任何线程(裸着来)# def main():#     start_time = time.time()#     for i in range(10):#         start()#     print(time.time()-start_time)# if __name__ == '__main__':#     main()def main():
    start_time = time.time()
    thread_name_time = {}# 我们先创建个字典 (thread_name_time) 用来来存储我们每个线程的名称与对应的时间

    for i in range(10):        # 也就是说,每个线程顺序执行
        thread = threading.Thread(target=start)# target=写你要多线程运行的函数,不需要加括号
        thread.start()# 上一行开启了线程,这一行是开始运行(也就是开启个 run)
        thread_name_time[i] = thread # 添加数据到我们的字典当中,这里为什么要用i做key?这是因为这样方便我们join

    for i in range(10):
        thread_name_time[i].join()    #     join() 等待线程执行完毕(也就是说卡在这里,这个线程执行完才会执行下一步)
    print(time.time()-start_time)if __name__ == '__main__':
    main()

输出

6.2037984102630615
# 6.553307056427002 裸奔# 6.2037984102630615 单线程顺序执行# 6.429047107696533 线程并发

我们可以看到,速度上的区别不大。

多线程并发不如单线程顺序执行快

这是得不偿失的

造成这种情况的原因就是 GIL

这里是计算密集型,所以不适用

在我们执行加减乘除或者图像处理的时候,都是在从 CPU 上面执行才可以。Python 因为 GIL 存在,同一时期肯定只有一个线程在执行,这样这样就是造成我们开是个线程和一个线程没有太大区别的原因。

而我们的网络爬虫大多时候是属于 IO 密集与计算机密集
在这里插入图片描述
BIOS:B:Base、I:Input、O:Output、S:System

也就是你电脑一开机的时候就会启动。

1. 计算密集型

在上面的时候,我们开启了两个线程,如果这两个线程要同时执行,那同一时期 CPU 上只有一个线程在执行。

那从上图可知,那这两个线程就需要频繁的在上下文切换。

Ps:我们这个绿色表示我们这个线程正在执行,红色代表阻塞。

所以,我们可以明显的观察到,线程的上下文切换也是需要消耗资源的(时间-ms)不断的归还和拿取 GIL 等,切换上下文。明显造成很大的资源浪费。

2. IO 密集型

我们现在假设,有个服务器程序(Socket)也就是我们新开的一个程序(也就是我们网络爬虫的最底层)开始爬取目标网页了,我们那个网页呢,有两个线程同时运行,我们线程二已经请求成功开始运行了,也就是上图的 (Thread 2)绿色一条路过去。

而我们的线程一(Thread 1)- Datagram(这里它开启了一个 UDP),然后等待数据建立(也就是等待哪些 HTML、CSS 等数据返回)也就是说,在 Ready to receive(recvfrom)之间都是准备阶段。这样就是有一段时间一直阻塞,而我们的线程二可以一直无停歇也不用切换上下文就一直在运行。这样的 IO 密集型就有很大的好处。

IO 密集型,这样就把我们等待的时间计算进去了,节省了大部分时间。

这里我们需要注意的是,我们的多线程是运行在 IO 密集型上的,我们得区分清楚。

还有就是,资源等待,比如有时候我们使用浏览器发起了一个 Get 请求,那浏览器图标上面在转圈圈的时候就是我们请求资源等待的时间,(也就是图上面的 Datagram 到 Ready to receive )数据建立到数据接收(就是转圈圈的时间)。我们完全就不需要执行它,就让它等待就好。这个时候让另一个线程去执行就好

换言之就是:第一个线程,我们爬取那个网页转圈圈的时候让另一个线程继续爬取。这样就避免了资源浪费。(把时间都利用起来)

注意: 请求资源是不需要 CPU 进行计算的,CPU 参与是很少的,而我们第一个例子,计算数字的 for 循环中,是需要 CPU 进行计算的。

5.1 Thread 直接创建子线程

5.1.1 非守护线程

复杂的操作之前需要一个简单的示例开始:

import threading, timedef start():
    time.sleep(1)
    print(threading.current_thread().name) # 当前线程名称
    print(threading.current_thread().is_alive()) # 当前线程状态
    print(threading.current_thread().ident) # 当前线程的编号print('start')# 要使用多线程哪个函数>>>target=函数,name=给这个多线程取个名字# 如果你不起一个名字的话,那那它会自己去起一个名字的(pid)也就是个 ident# 类似声明thread = threading.Thread(target=start,name='my first thread')# 每个线程写完你不start()的话,就类似只是声明thread.start()
print('stop')# 输出start
stop
my first threadTrue2968

如果有参数的话,我们就对多线程参数进行传参数。代码示例:

import threading, timedef start(num):
    time.sleep(num)
    print(threading.current_thread().name)
    print(threading.current_thread().isAlive())
    print(threading.current_thread().ident)

print('start')
thread = threading.Thread(target=start,name='my first thread', args=(1,))

thread.start()
print('stop')

解析:

我认认真看一下我们的运行结果,

start
stop
my first thread
True
2968

我们会发现并不是按我们正常的逻辑执行这一系列的代码。

而是,先执行完 start 然后就直接 stop 然后才会执行我们函数的其他三项。

一个线程它就直接贯穿到底了。也就是先把我们主线程里面的代码运行完,然后才会运行它里面的代码。

我们的代码并不是当代码执行到 thread.start() 等它执行完再执行 print(‘stop’) 。而是,我们线程执行到thread.start() 继续向下执行,同时再执行里面的代码(也就是**start()**函数里面的代码)。(不会卡在 thread.start() 那里) 也不会随着主线程结束而结束

因为,程序在执行到 print(‘stop’) 之后就是主线程结束,而里面开的线程是我们自己开的。当我们主线程执行这个 stop 就已经结束了。

这种不会随着主线程结束而销毁的,这种线程它叫做:非守护线程

主线程会跳过创建的线程继续执行;

直到创建线程运行完毕;

程序结束;

既然,有非守护线程。那就还有守护线程。不要急,我再举个非守护线程的例子。

首先,我们可以使用 Thread 类来创建一个线程,创建时需要指定 target 参数为运行的方法名称,如果被调用的方法需要传入额外的参数,则可以通过 Thread的 args参数来指定。示例如下:

import threading, timedef target(second):
    print(f'Threading {threading.current_thread().name} is runing')
    print(f'Threading {threading.current_thread().name} sleep {second}s')
    time.sleep(second)
    print(f'Threading {threading.current_thread().name} ended')

print(f'Threading {threading.current_thread().name} is runing')for i in [1, 5]:
    t = threading.Thread(target=target, args=[i])    # t = threading.Thread(target=target, args=(i,))
    t.start()
print(f'Threading {threading.current_thread().name} is ended')# 输出Threading MainThread is runing
Threading Thread-1 is runing
Threading Thread-1 sleep 1s
Threading Thread-2 is runing
Threading Thread-2 sleep 5s
Threading MainThread is ended
Threading Thread-1 ended
Threading Thread-2 ended

在这里我们首先声明了一个方法,叫作 target,它接收一个参数为 second,通过方法的实现可以发现,这个方法其实就是执行了一个 time.sleep 休眠操作,second参数就是休眠秒数,其前后都 print了一些内容,其中线程的名字我们通过 threading.current_thread().name 来获取出来,如果是主线程的话,其值就是 MainThread,如果是子线程的话,其值就是 Thread-*。

然后我们通过 Thead类新建了两个线程,target参数就是刚才我们所定义的方法名,args以列表的形式传递。两次循环中,这里 i 分别就是 1 和 5,这样两个线程就分别休眠 1 秒和 5 秒,声明完成之后,我们调用 start 方法即可开始线程的运行。

观察结果我们可以发现,这里一共产生了三个线程,分别是主线程 MainThread和两个子线程 Thread-1、Thread-2。另外我们观察到,主线程首先运行结束,紧接着 Thread-1、Thread-2 才接连运行结束,分别间隔了 1 秒和 4 秒。这说明主线程并没有等待子线程运行完毕才结束运行,而是直接退出了,有点不符合常理。

如果我们想要主线程等待子线程运行完毕之后才退出,可以让每个子线程对象都调用下 join方法,实现如下:

for i in [1, 5]:
    t = threading.Thread(target=target, args=[i])
    t.start()
    t.join()# 输出Threading MainThread is runing
Threading Thread-1 is runing
Threading Thread-1 sleep 1s
Threading Thread-1 ended
Threading Thread-2 is runing
Threading Thread-2 sleep 5s
Threading Thread-2 ended
Threading MainThread is ended

这样,主线程必须等待子线程都运行结束,主线程才继续运行并结束。

5.2 继承 Thread 类创建子线程

另外,我们也可以通过继承 Thread 类的方式创建一个线程,该线程需要执行的方法写在类的 run 方法里面即可。上面的例子的等价改写为:

import threading, timeclass 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 runing')
        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 runing')for i in [1, 5]:
    t = MyThread(i)
    t.start()
    t.join()
print(f'Threading {threading.current_thread().name} is ended')# 输出Threading MainThread is runing
Threading Thread-1 is runing
Threading Thread-1 sleep 1s
Threading Thread-1 is ended
Threading Thread-2 is runing
Threading Thread-2 sleep 5s
Threading Thread-2 is ended
Threading MainThread is ended

可以看到,两种实现方式,其运行效果是相同的。

5.3 守护线程

在线程中有一个叫作守护线程的概念,如果一个线程被设置为守护线程,那么意味着这个线程是“不重要”的,这意味着,如果主线程结束了而该守护线程还没有运行完,那么它将会被强制结束。在 Python 中我们可以通过 setDaemon方法来将某个线程设置为守护线程。

如果要修改成守护线程,那你就得在 thread.start()前面加一个:

需要在我们启动之前设置。

示例一如下:

添加之前:

import threading, timedef start(num):
    time.sleep(num)
    print(threading.current_thread().name) # 当前线程的名字
    print(threading.current_thread().isAlive())
    print(threading.current_thread().ident)

print('start') # 主线程开始thread = threading.Thread(target=start,name='my first thread', args=(1,))# 可以使用 for 循环来添加多个thread.start()
print('stop') # 主线程结束# 运行结果start
stop
my first threadTrue15816

添加之后:

import threading, timedef start(num):
    time.sleep(num)
    print(threading.current_thread().name) # 当前线程的名字
    print(threading.current_thread().isAlive())
    print(threading.current_thread().ident)

print('start') # 主线程开始thread = threading.Thread(target=start,name='my first thread', args=(1,))# 可以使用 for 循环来添加多个thread.setDaemon(True) # 在 start 开始之前设置thread.start()
print('stop') # 主线程结束# 运行结果start
stop

我们可以看见,程序直接运行:start、stop,执行到 print(‘stop’) 它就结束了。也就随着我们的主线程结束而结束。并不管它里面还有什么没有执行完。(也不会管他里面的 time.sleep())我们的主线程一结束,我们的守护线程就会随着主线程一起销毁。

我们日常启动的是非守护线程,守护线程用的较少。

守护线程会伴随主线程一起结束,setDaemon设置为 True 即可。

示例二如下:

添加之前:

import threading, timedef target(second):
    print(f'Threading {threading.current_thread().name} is runing')
    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 runing')
t1 = threading.Thread(target=target, args=[2])
t1.start()
t2 = threading.Thread(target=target, args=[5])
t2.start()
print(f'Threading {threading.current_thread().name} is ended')# 运行结果Threading MainThread is runing
Threading Thread-1 is runing
Threading Thread-1 sleep 2s
Threading Thread-2 is runing
Threading Thread-2 sleep 5s
Threading MainThread is ended
Threading Thread-1 is ended
Threading Thread-2 is ended

添加之后:

import threading, timedef target(second):
    print(f'Threading {threading.current_thread().name} is runing')
    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 runing')
t1 = threading.Thread(target=target, args=[2])
t1.start()
t2 = threading.Thread(target=target, args=[5])
t2.setDaemon(True)
t2.start()
print(f'Threading {threading.current_thread().name} is ended')# 运行结果Threading MainThread is runing
Threading Thread-1 is runing
Threading Thread-1 sleep 2s
Threading Thread-2 is runing
Threading Thread-2 sleep 5s
Threading MainThread is ended
Threading Thread-1 is ended

在这里我们通过 setDaemon方法将 t2设置为了守护线程,这样主线程在运行完毕时,t2线程会随着线程的结束而结束。

运行结果:

Threading MainThread is runing
Threading Thread-1 is runing
Threading Thread-1 sleep 2s
Threading Thread-2 is runing
Threading Thread-2 sleep 5s
Threading MainThread is ended
Threading Thread-1 is ended

可以看到,我们没有看到 Thread-2打印退出的消息,Thread-2 随着主线程的退出而退出了。

不过细心的你可能会发现,这里并没有调用 join方法,如果我们让 t1和 t2都调用 join方法,主线程就会仍然等待各个子线程执行完毕再退出,不论其是否是守护线程。

5.4 互斥锁

接下来是比较难的知识点,还是从简单的知识点开始。

比方说我们现在有两个线程,一个是求加一千万次,另一个是减一千万次。按原本得计划来说,一个加一千万一个减一千万结果应该还是零。可是最终得结果并不是等于零,我们多运行几次会发现几次得出来得结果并不相同。多线程代码如下:

import threadingimport time

number = 0def addNumber(i):
    time.sleep(i)    global number    for i in range(1000000):
        number += 1
    print("加",number)def downNumber(i):
    time.sleep(i)    global number    for i in range(1000000):
        number -= 1
    print("减",number)

print("start") # 输出一个开始thread = threading.Thread(target = addNumber, args=(2,)) #开启一个线程(声明)thread2 = threading.Thread(target = downNumber, args=(2,)) # 开启第二个线程(声明)thread.start() # 开始thread2.start() # 开始thread.join()
thread2.join()# join 阻塞在这里,直到我们得阻塞线程执行完毕才会向下执行print("外", number)
print("stop")

就算单线程也会出现两个值:1000000 与 -1000000,两个函数谁先运行就是输出谁的结果,为什么呢?因为两个函数调用的是全局变量 number 所以,如果先运行加法函数,加法得到的结果是 1000000 ,那全局下的 number 的值也会变成:1000000 ,那减法的操作亦然就是 0。反过来也是一个意思。代码如下:

import threadingimport time

number = 0def addNumber(i = None):
    # time.sleep(i)
    global number    for i in range(1000000):
        number += 1
    print("加",number)def downNumber(i = None):
    # time.sleep(i)
    global number    for i in range(1000000):
        number -= 1
    print("减",number)

addNumber()
downNumber()
print(number)# 运行结果加 1000000减 00# 反过来运行downNumber()
addNumber()
print(number)# 运行结果减 -1000000加 00# 再来一个差不多的例子:import threadingimport time

number = 0def addNumber():
    global number    for i in range(1000000):
        number += 1
    print("加",number)    return numberdef downNumber():
    global number    for i in range(1000000):
        number -= 1
    print("减",number)    return number

sum_num = downNumber() + addNumber()
print("Result", sum_num)# 输出减 -1000000加 0Result -1000000# 修改以下代码,其他不变:sum_num = addNumber() + downNumber()# 输出加 1000000减 0Result 1000000

由上面的多线程代码,我可以发现结果:两个线程操作同一个数字,最后得到的数字是混乱的。为什么说是混乱的呢?

我们现在所要做的是一个赋值,number += 1 其实也就是 number = number + 1,的这个操作。而在我们的 Python 当中,我们是先:计算右边的,然后赋值给左边的,一共两步。

我先来看一下正确的运行流程:

# 我们的 number = 0# 第一步是先运行我们的代码:a = number + 1 # 等价于 0+1=1 # 也就是先运行右边的,然后赋值给 anumber = a # 然后,再把 a 的结果赋值个 number# 上面运行完加法之后,我们加下来运行减肥的操作。b = number - 1 # 等价于 1-1 = 0# 然后,赋值个 number# 最后 number 等于 0number = 0

上面的过成是正确的流程,可在多线程里面呢?

number = 0 # 开始初始值 0a = number+1 # 等价于 0+1=1# 这个地方要注意!!!# 在运行完上面一步的时候,还没来得急把结果赋值给 number# 就开始运行减法操作:b = number-1 # 等价于 0-1=-1# 然后,这两个运行结束之后就被赋值:number=b # b = -1number=a # a = 1# 最终得结果为:number = 1

上面就是我们刚才结果错乱得原因,也就是说:我们计算和赋值是两部分,但是该多线程它没有顺序执行,这也就是我们所说的线程不安全。

因为,执行太快了,两个线程交互交织在一起,最终得到我们这个错误结果。以上就是线程不安全的问题。

这就是需要 Lock 锁,给它上一把锁,来达到我们 number 的效果,这个时候为了避免错误,我们要给他上一把锁了。再给你讲解上锁之前呢,接下来,我们来讲一点复杂的例子:

在一个进程中的多个线程是共享资源的

比如

在一个进程中,有一个全局变量 count 用来计数,现在我们声明多个线程,每个线程运行时都给 count加 1,让我们来看看效果如何,代码实现如下:

import threading, time

count = 0class 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(len(threads))print(f'Final count: {count}')

在这里,我们声明了 1000 个线程,每个线程都是现取到当前的全局变量 count 值,然后休眠一小段时间,然后对 count 赋予新的值。

那这样,按照常理来说,最终的 count 值应该为 1000。但其实不然,我们来运行一下看看。

运行结果如下:

Final count: 69

最后的结果居然只有 69,而且多次运行或者换个环境运行结果是不同的。

这是为什么呢?

因为 count 这个值是共享的,每个线程都可以在执行 temp = count 这行代码时拿到当前 count 的值,但是这些线程中的一些线程可能是并发或者并行执行的,这就导致不同的线程拿到的可能是同一个 count 值,最后导致有些线程的 count 的加 1 操作并没有生效,导致最后的结果偏小。

所以,如果多个线程同时对某个数据进行读取或修改,就会出现不可预料的结果。为了避免这种情况,我们需要对多个线程进行同步,要实现同步,我们可以对需要操作的数据进行加锁保护,这里就需要用到 threading.Lock了。

加锁保护是什么意思呢?

就是说,某个线程在对数据进行操作前,需要先加锁,这样其他的线程发现被加锁了之后,就无法继续向下执行,会一直等待锁被释放,只有加锁的线程把锁释放了,其他的线程才能继续加锁并对数据做修改,修改完了再释放锁。这样可以确保同一时间只有一个线程操作数据,多个线程不会再同时读取和修改同一个数据,这样最后的运行结果就是对的了。

我们可以将代码修改为如下内容:

示例一的修改:

import threadingimport time

lock = threading.Lock() # 创建一个最简单的 读写锁number = 0def addNumber():
    global number    for i in range(1000000):
        lock.acquire() # 先获取
        number += 1
        # 中间的这个过程让他强制有这个计算和赋值的过程,也就是让他执行完这两个操作,后再切换。
        # 这样就不会完成计算后,还没来的及赋值就跑到下一个去了。
        # 这样也就防止了线程不安全的情况
        lock.release() # 再释放def downNumber():
    global number    for i in range(1000000):
        lock.acquire()
        number -= 1
        lock.release()

print("start") # 输出一个开始thread = threading.Thread(target = addNumber) #开启一个线程(声明)thread2 = threading.Thread(target = downNumber) # 开启第二个线程(声明)thread.start() # 开始thread2.start() # 开始thread.join()
thread2.join()# join 阻塞在这里,直到我们得阻塞线程执行完毕才会向下执行print("外", number)
print("stop")# 输出start
外 0stop

在代码:lock.acquire() 与 lock.release() 中间的这个过程让它强制有这个计算和赋值的过程,也就是让他执行完这两个操作,后再切换。这样就不会完成计算后,还没来的及赋值就跑到下一个去了。这样也就防止了线程不安全的情况。

然后,就是我们第一个线程拿到这把锁的 lock.acquire() 了,那另一个线程就会在 lock.acquire() 阻塞了,直到我们另一个线程把 lock.release() 锁释放,然后拿到锁执行,就这样不断地切换拿锁执行。

死锁:就是前面的线程拿到锁之后,运行完却不释放锁,下一个线程在等待前一个线程释放锁,这种就是死锁。说的直白一点就是,相互等待。就像照镜子一样,你中有我,我中有你。也就是在没有 release 的这种情况。(你等我表白,我等你表白)

示例二的加锁

import threading, time

count = 0class MyThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)    def run(self):
        global count
        lock.acquire() # 获取锁
        temp = count + 1
        time.sleep(0.001)
        count = temp
        lock.release() # 释放锁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}')

在这里我们声明了一个 lock 对象,其实就是 threading.Lock 的一个实例,然后在 run 方法里面,获取 count 前先加锁,修改完 count 之后再释放锁,这样多个线程就不会同时获取和修改 count 的值了。

运行结果如下:

Final count: 1000

这样运行结果就正常了。

关于 Python 多线程的内容,这里暂且先介绍这些,关于 theading更多的使用方法,如信号量、队列等,可以参考官方文档:https://docs.python.org/zh-cn/3.7/library/threading.html#module-threading。

5.5 递归锁 RLOCK

再次复用,一个锁可以再嵌套一个锁。向我们上面的普通锁,一个线程里面,你只能获取一次。如果获取第二次就会报错。

递归锁什么时候用呢?需要更低精度的,力度更小,为了更小的力度。

import threadingimport timeclass Test:
    rlock = threading.RLock()    def __init__(self):
        self.number = 0

    def execute(self, n):
        # 原本是获取锁和释放锁,那如果有时候你忘记了写 lock.release() 那就变成了死锁。
        # 而 with 可以解决这个问题。
        with Test.rlock:            # with 内部有个资源释放的机制
            self.number += n    def add(self):
        with Test.rlock:
            self.execute(1)    def down(self):
        with Test.rlock:
            self.execute(-1)def add(test):
    for i in range(1000000):
        test.add()def down(test):
    for i in range(1000000):
        test.down()if __name__ == '__main__':
    thread = Test() # 实例化
    t1 = threading.Thread(target=add, args=(thread,))
    t2 = threading.Thread(target=down, args=(thread,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    print(t.number)

我们会发现这个递归锁是比较耗费时间的,也就死我们获取锁与释放锁都是进行上下文切换导致资源消耗的,所以说开启的锁越多,所耗费的资源也就越多,程序的运行速度也就越慢。一些大的工程很少上这么多的锁,因为这个锁的速度会拖慢你整个程序的运行速度。所以得思考好,用不用这些东西。

5.6 Python 多线程的问题

由于 Python 中 GIL 的限制,导致不论是在单核还是多核条件下,在同一时刻只能运行一个线程,导致 Python 多线程无法发挥多核并行的优势。

GIL 全称为 Global Interpreter Lock,中文翻译为全局解释器锁,其最初设计是出于数据安全而考虑的。

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

获取 GIL

执行对应线程的代码

释放 GIL

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

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

5.7 避免 GIL
在这里插入图片描述
前面开头已经提到:因为 GIL 的存在,所以不管我们开了多少线程,同一时间始终只有一个线程在执行。那我们该如何避免 GIL 呢?

那这样的话,我们不开线程不就行,(它的的存在已经无法避免,那我们选择不使用它不就相当于不存在嘛)。那这是,你会想:那不开线程我们开啥呢?

问的好!

我们来开:进程,那怎么说?别急!请听我细细道来。

比方你有 3 个 CPU(当然,你可能有更多,这里就按 3 个 CPU来为例子),那我们就开 3 个进程就好。一个 CPU 上运行就好。

Ps:我们的进程是可以同时运行的。

我们可以看一下下面的图片:

任务管理器
在这里插入图片描述
我们 任务管理 上的每一项都是一个进程。

多进程比多线程不好的地方是什么呢?

多进程的创建和销毁开销也会更大,成本高。

你可能线程可以开许多的线程,但你的进程就是看你的 CPU 数量。

进程间无法看到对方数据,需要使用栈或者队列进行获取。

每个进程之间都是独立的。

就好像我们上面的谷歌浏览器和我们的 Pycharm 是没有任何关系的,谷歌浏览器上面的数据肯定不可能让 Pycharm 看到。这就是我们所说的进程之间的独立性。

如果你想要一个进行抓取数据,一个进行调用数据,那这时是不能直接调用的,需要你自己定义个结构才能使用。>>> 编程复杂度提升。

学员问题:任务管理器上面超过五六个进程。都是进程的话,怎么能开那么多呢?

答:我们一个 CPU 不止能执行一个进程,就比如我的一个 CPU 里面密麻麻有许多进程。(比方我现在开六个进程)并发执行的。只不过计算机执行的速度非常快,这里我简单讲一下哈。这是计算机原理的课。

不管是任何操作系统,现在就拿单核操作系统来说:我们假设现在只有一个 CPU ,一个 CPU 里面六个进程,同一时间它只有一个进程在运行。不过我们计算执行速度非常快,这个程序执行完,它就会执行一个上下文切换,执行下一个。(因为,它执行的速度非常快,你就会感觉是并发执行一样。)

实际上,一个 CPU 同一时间只有一个进程在执行,一个进程里面它只有一个线程在执行。(当然,这个单核是五六年前了。现在肯定至少有双核。

那就说有第二个 CPU 了。

而第二个和 CPU 上面又有许多个 进程,两个 CPU 是互不相干。

那这时候,第一个 CPU 上面运行一个进程,而我们的第二个 CPU 上面也有一个进程,两个是互补相干。(就相当于你开了两台电脑。)

但是同一个 CPU 在同一时间只有一个就进程。(不管你(电脑)速度多么快,实际上本质上(在那一秒)只有一个进程在执行。如果你是双核,那就有两个进程。(四核就有四个进程)

Python 有个不好的地方,刚刚上面讲到,如果我们有两个 CPU 那就有两个进程在执行(那四个 CPU 就是四个进程在执行),但是因为 Python 当中存在着 GIL,它即使有四个 CPU 每次也只有一个线程能进去,也就是说:同一时间当中,一个 CPU 上的一个进程中的一个线程在执行。剩下的都不能运行,我们的 Python 不能利用多核。

如果,大家用的是 C、Java、Go 这种的就没有这个说法了。

Concurrent.Thread.js多线程执行大批量函数 介绍了一个可以在JavaScript中应用多线程的库:Concurrent.Thread,内有多线程库脚本,以及使用说明和实例,如果查看详情,可以查看我的博客https://blog.youkuaiyun.com/hsl_1990_08_15/article/details/84765772 立即下载

相关推荐

Concurrent.Thread

Concurrent.Thread.js插件从20071219到20090713各个版本都有,简单做了打包,资源分数不能设置为0,大家可以到我的百度云盘分享链接下载 https://pan.baidu.com/s/1iD0DbIkUDWQ9OZHMP4hsig 提取码:xsy4

进程与线程、多进程与多线程

进程与线程 1.概念: 进程:进程可以简单的理解为一个可以独立运行的程序单位,它是线程的集合,进程就是有一个或多个线程构成的。 线程:线程是进程中的实际运行单位,是操作系统进行运算调度的最小单位。可理解为线程是进程中的一个最小运行单元。 多进程:多进程就是指计算机同时执行多个进程,一般是同时运行多个软件。 多线程:是指从软件或者硬件上实现多个线程并发执行的技术。 多进程优点: 1.每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系; 2.通过增加CPU,就可以容易扩充性能; 3.可以尽量减少线程加锁/

u014709124的博客 490

Concurrent.Thread.js javascript多线程

Concurrent.Thread.js 一个用来让javascript也进行多线程开发的包,感兴趣的快来下吧。

多进程与多线程

进程:电脑打开多个软件,如:qq、微信、浏览器,电脑为每个打开的应用都会分配进程,在任务管理器中我们可以看到多个正在运行的进程。 线程:线程是进程的执行单元,一个进程一般只有一个主线程,为了提高效率有些程序会采用多线程,例如:在有些网络文件下载时,为了提高效率,程序将下载的文件分成多个部分,然后同时进行下载。 ...

m0_45143354的博客 528

Java多线程ThreadAPI详细介绍

主要介绍了Java多线程ThreadAPI详细介绍,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

PLC模拟量输入电路设计:±10V与±20mA信号调理全解析

在工业自动化和数据采集系统中,模拟量输入是连接物理世界与数字控制的核心桥梁。无论是变送器输出的4-20mA电流环,还是伺服驱动与信号发生器提供的±10V电压信号,PLC和采集模块都需要在复杂现场环境下稳定、精准地完成信号采集。理解信号体制的原理——电流环抗长线干扰、电压环响应快——是设计模拟输入前端的基础。通过精密电阻网络实现分压与采样,利用运放完成双极性到单极性的电平搬移,配合TVS与滤波电路保障可靠性,并经过ADC量化与校准流程,最终构建出兼顾精度、成本和工业级鲁棒性的完整信号链路。本文面向硬件工程师与

weixin_29163797的博客 301

43. 使用多进程

由于Python中全局解释器锁(GIL)的存在,在任意时刻只允许一个线程在解释器中运行,因此Python的多线程不适合处理CPU密集型的任务。 要求:想要处理CPU密集型的任务,可以使用多进程模型。 解决方案: 使用标准库中multiprocessing.Process类,它可以确定子进程执行任务。操作接口、进程间通信、进程加同步等都与threading.Thread类类似。 对于multip...

变成习惯 357

卡尔曼滤波五步拆解:用Excel手推姿态解算全过程

本文通过Excel手推演示,详细拆解了卡尔曼滤波在姿态解算中的五步核心流程。文章以融合陀螺仪与加速度计数据估算角度为例,从状态预测、协方差更新到增益计算,逐步展示如何将抽象的矩阵运算转化为可视化的表格计算,帮助读者直观理解滤波器的“预测-修正”机制,并为算法实现与参数调优提供清晰指导。

hhh00的博客 984

多进程和多线程的概念

一、多线程是什么? 说起多线程,那么就不得不说什么是线程,而说起线程,又不得不说什么是进程。 进程可以简单的理解为一个可以独立运行的程序单位,它是线程的集合,进程就是有一个或多个线程构成的。而线程是进程中的实际运行单位,是操作系统进行运算调度的最小单位。可理解为线程是进程中的一个最小运行单元。 那么多线程就很容易理解:多线程就是指一个进程中同时有多个线程正在执行。 为什么要使用多线程? 在一个程序中,有很多的操作是非常耗时的,如数据库读写操作,IO操作等,如果使用单线程,那么程序就必须等待这些操作执行完成之

Smilecoc的博客 494

【大唐杯学习超快速入门】5G技术原理仿真教学——5G网络开通调测与车联网

自用笔记,比较乱。

qq_41224270的博客 1万+

【Milvus】实战指南:利用milvus-backup实现高效数据备份与恢复

本文详细介绍了如何利用官方工具milvus-backup为Milvus向量数据库实施高效的数据备份与恢复。文章从备份的必要性出发,逐步讲解了环境准备、配置要点、全量与按需备份操作,以及恢复数据的关键步骤和验证方法,旨在帮助用户构建可靠的数据安全防线,应对误操作或系统故障风险。

weixin_29217235的博客 133

python 多线程并行_熬了两个通宵写的!终于把多线程和多进程彻底讲明白了!...

我们知道,在一台计算机中,我们可以同时打开许多软件,比如同时浏览网页、听音乐、打字等等,看似非常正常。但仔细想想,为什么计算机可以做到这么多软件同时运行呢?这就涉及到计算机中的两个重要概念:多进程和多线程了。(PS:万字长文,讲得很详细,建议先收藏再好好看!)同样,在编写爬虫程序的时候,为了提高爬取效率,我们可能想同时运行多个爬虫任务。这里同样需要涉及多进程和多线程的知识。本文,我们就先来了解一下...

weixin_39589644的博客 693

Nano Banana 2哪里使用?Nano Banana 2提示词合集和使用教程

收集最强的Nano Banana 2提示词合集,以及Nano Banana 2哪里使用的教程,并且有10个免费使用渠道。

qq_42927225的博客 2006

python爬虫进程和线程的区别_熬了两个通宵写的!终于把多线程和多进程彻底讲明白了!...

我们知道,在一台计算机中,我们可以同时打开许多软件,比如同时浏览网页、听音乐、打字等等,看似非常正常。但仔细想想,为什么计算机可以做到这么多软件同时运行呢?这就涉及到计算机中的两个重要概念:多进程和多线程了。(PS:万字长文,讲得很详细,建议先收藏再好好看!)同样,在编写爬虫程序的时候,为了提高爬取效率,我们可能想同时运行多个爬虫任务。这里同样需要涉及多进程和多线程的知识。本文,我们就先来了解一下...

weixin_39974958的博客 183

停车位检测:基于YOLOv5与UI界面的实现

YOLOv5是YOLO(You Only Look Once)系列中的最新版本,它能够在单次前向传播中同时完成多个目标的定位与分类,因此被广泛应用于实时目标检测任务。与之前的版本相比,YOLOv5在精度和速度上得到了很大的优化,适合嵌入到实际的自动驾驶和智能交通系统中。YOLOv5使用了卷积神经网络(CNN)进行特征提取,并通过回归方法预测边界框、类别及置信度。高效的推理速度:YOLOv5采用了优化的模型结构,能够在保证精度的同时提供较快的推理速度。较高的精度。

YOLO 696

AutoJs调用DeepSeek大模型接口实战教程 AutoJs接入DeepSeek、 DeepSeek API调用 、 手机脚本接入大模型

通过这篇文章,我们用 AutoJs 成功接入了 DeepSeek Chat API。使用发送请求在 Header 里正确携带 API Key从里拿到最终返回结果这样一来,你就可以在 AutoJs 里实现一个智能助手脚本,甚至做更复杂的 AI 自动化工具。

代码简单说 Vue、JAVA、PHP、Node.js 熟练运用,接口、架构、性能全搞定。 476

调试多线程 BUG 熬 2 夜,老鸟一句话点醒:“你没锁资源啊”

不对,张哥进一步举例,假设库存只有 1,线程 A 和线程 B 同时查询到库存为 1,然后线程 A 先扣减,库存变为 0,线程 B 再扣减,库存就变成了 - 1,这就出现了超卖现象。然而,在修改完成后,我又陷入了思考:如果我们系统中有很多种商品,每种商品都有自己的库存,那么当多个线程同时处理不同商品的库存扣减操作时,使用synchronized修饰整个deductStock方法会导致所有线程都只能串行执行,即使它们处理的是不同商品的库存,这会大大降低系统的并发处理效率。修改完代码后,我再次进行了压力测试。

2503_92848840的博客 450

倍福--实现TCP/IP的配置

控制器为CX5120,需要和上位机做通信,选择TCP/IP的通讯方式,本文介绍TCP/IP通信的操作步骤。 目 录 软硬件版本 3 1.1. 倍福Beckhoff 3 1.1.1. 控制器硬件 3 1.1.2. 控制软件 3 1.2. 串口调试助手 3 1.2.1. TCP调试通信助手 3 授权文件 3 2.1 Lib文件 3 2.2. CAB文件 4 PLC程序编写 4 3.1. 引入TCP/IP库文件 4 3.2. Client端程序 5 3.2.1. 建立相关变量 5 3.3.2. 编写连接功能块

weixin_41883890的博客 5730

肝通宵写了三万字把SQL数据库的所有命令,函数,运算符讲得明明白白讲解,内容实在丰富,建议收藏+三连好评

Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,不要看完;在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。恭喜你,如果学到这里,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web 渗透、安全服务、安全分析等岗位;如果表中的字段是可选的,则可以在不向该字段添加值的情况下插入新记录或更新记录。

2401_84239830的博客 541

深度学习工具包 DeepLearningToolbox

深度学习工具包Deprecation notice.-----This toolbox is outdated and no longer maintained.There are much better tools available for deep learning than this toolbox, e.g. [Theano](http://deeplearning.net/software/theano/), [torch](http://torch.ch/) or [tensorflow](http://www.tensorflow.org/)I would suggest you use one of the tools mentioned above rather than use this toolbox.Best, Rasmus.DeepLearnToolbox================A Matlab toolbox for Deep Learning.Deep Learning is a new subfield of machine learning that focuses on learning deep hierarchical models of data.It is inspired by the human brain's apparent deep (layered, hierarchical) architecture.A good overview of the theory of Deep Learning theory is[Learning Deep Architectures for AI](http://www.iro.umontreal.ca/~bengioy/papers/ftml_book.pdf)For a more informal introduction, see the following videos by Geoffrey Hinton and Andrew Ng.* [The Next Generation of Neural Networks](http://www.youtube.com/watch?v=AyzOUbkUf3M) (Hinton, 2007)* [Recent Developments in Deep Learning](http://www.youtube.com/watch?v=VdIURAu1-aU) (Hinton, 2010)* [Unsupervised Feature Learning and Deep Learning](http://www.youtube.com/watch?v=ZmNOAtZIgIk) (Ng, 2011)If you use this toolbox in your research please cite [Prediction as a candidate for learning deep hierarchical models of data](http://www2.imm.dtu.dk/pubdb/views/publication_details.php?id=6284)```@MASTERSTHESIS\{IMM2012-06284, author = "R. B. Palm", title = "Prediction as a candidate for learning deep hierarchical models of data", year = "2012",}```Contact: rasmusbergpalm at gmail dot comDirectories included in the toolbox-----------------------------------`NN/` - A library for Feedforward Backpropagation Neural Networks`CNN/` - A library for Convolutional Neural Networks`DBN/` - A library for Deep Belief Networks`SAE/` - A library for Stacked Auto-Encoders`CAE/` - A library for Convolutional Auto-Encoders`util/` - Utility functions used by the libraries`data/` - Data used by the examples`tests/` - unit tests to verify toolbox is workingFor references on each library check REFS.mdSetup-----1. Download.2. addpath(genpath('DeepLearnToolbox'));Example: Deep Belief Network---------------------```matlabfunction test_example_DBNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit RBM and visualize its weightsrand('state',0)dbn.sizes = [100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);figure; visualize(dbn.rbm{1}.W'); % Visualize the RBM weights%% ex2 train a 100-100 hidden unit DBN and use its weights to initialize a NNrand('state',0)%train dbndbn.sizes = [100 100];opts.numepochs = 1;opts.batchsize = 100;opts.momentum = 0;opts.alpha = 1;dbn = dbnsetup(dbn, train_x, opts);dbn = dbntrain(dbn, train_x, opts);%unfold dbn to nnnn = dbnunfoldtonn(dbn, 10);nn.activation_function = 'sigm';%train nnopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.10, 'Too big error');```Example: Stacked Auto-Encoders---------------------```matlabfunction test_example_SAEload mnist_uint8;train_x = double(train_x)/255;test_x = double(test_x)/255;train_y = double(train_y);test_y = double(test_y);%% ex1 train a 100 hidden unit SDAE and use it to initialize a FFNN% Setup and train a stacked denoising autoencoder (SDAE)rand('state',0)sae = saesetup([784 100]);sae.ae{1}.activation_function = 'sigm';sae.ae{1}.learningRate = 1;sae.ae{1}.inputZeroMaskedFraction = 0.5;opts.numepochs = 1;opts.batchsize = 100;sae = saetrain(sae, train_x, opts);visualize(sae.ae{1}.W{1}(:,2:end)')% Use the SDAE to initialize a FFNNnn = nnsetup([784 100 10]);nn.activation_function = 'sigm';nn.learningRate = 1;nn.W{1} = sae.ae{1}.W{1};% Train the FFNNopts.numepochs = 1;opts.batchsize = 100;nn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.16, 'Too big error');```Example: Convolutional Neural Nets---------------------```matlabfunction test_example_CNNload mnist_uint8;train_x = double(reshape(train_x',28,28,60000))/255;test_x = double(reshape(test_x',28,28,10000))/255;train_y = double(train_y');test_y = double(test_y');%% ex1 Train a 6c-2s-12c-2s Convolutional neural network %will run 1 epoch in about 200 second and get around 11% error. %With 100 epochs you'll get around 1.2% errorrand('state',0)cnn.layers = { struct('type', 'i') %input layer struct('type', 'c', 'outputmaps', 6, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %sub sampling layer struct('type', 'c', 'outputmaps', 12, 'kernelsize', 5) %convolution layer struct('type', 's', 'scale', 2) %subsampling layer};cnn = cnnsetup(cnn, train_x, train_y);opts.alpha = 1;opts.batchsize = 50;opts.numepochs = 1;cnn = cnntrain(cnn, train_x, train_y, opts);[er, bad] = cnntest(cnn, test_x, test_y);%plot mean squared errorfigure; plot(cnn.rL);assert(er<0.12, 'Too big error');```Example: Neural Networks---------------------```matlabfunction test_example_NNload mnist_uint8;train_x = double(train_x) / 255;test_x = double(test_x) / 255;train_y = double(train_y);test_y = double(test_y);% normalize[train_x, mu, sigma] = zscore(train_x);test_x = normalize(test_x, mu, sigma);%% ex1 vanilla neural netrand('state',0)nn = nnsetup([784 100 10]);opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samples[nn, L] = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.08, 'Too big error');%% ex2 neural net with L2 weight decayrand('state',0)nn = nnsetup([784 100 10]);nn.weightPenaltyL2 = 1e-4; % L2 weight decayopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex3 neural net with dropoutrand('state',0)nn = nnsetup([784 100 10]);nn.dropoutFraction = 0.5; % Dropout fraction opts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex4 neural net with sigmoid activation functionrand('state',0)nn = nnsetup([784 100 10]);nn.activation_function = 'sigm'; % Sigmoid activation functionnn.learningRate = 1; % Sigm require a lower learning rateopts.numepochs = 1; % Number of full sweeps through dataopts.batchsize = 100; % Take a mean gradient step over this many samplesnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex5 plotting functionalityrand('state',0)nn = nnsetup([784 20 10]);opts.numepochs = 5; % Number of full sweeps through datann.output = 'softmax'; % use softmax outputopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, train_x, train_y, opts);[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');%% ex6 neural net with sigmoid activation and plotting of validation and training error% split training data into training and validation datavx = train_x(1:10000,:);tx = train_x(10001:end,:);vy = train_y(1:10000,:);ty = train_y(10001:end,:);rand('state',0)nn = nnsetup([784 20 10]); nn.output = 'softmax'; % use softmax outputopts.numepochs = 5; % Number of full sweeps through dataopts.batchsize = 1000; % Take a mean gradient step over this many samplesopts.plot = 1; % enable plottingnn = nntrain(nn, tx, ty, opts, vx, vy); % nntrain takes validation set as last two arguments (optionally)[er, bad] = nntest(nn, test_x, test_y);assert(er < 0.1, 'Too big error');```[![Bitdeli Badge](https://d2weczhvl823v0.cloudfront.net/rasmusbergpalm/deeplearntoolbox/trend.png)](https://bitdeli.com/free "Bitdeli Badge")

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

Python GIL详解:多线程为何慢?并发选型实战指南

并发编程是提升程序性能的关键手段,但许多开发者在使用Python多线程时会发现,CPU密集型任务不仅没有加速,反而更慢。这背后的核心约束是GIL(全局解释器锁),它保证同一时刻只有一个线程执行Python字节码,也解释了为何多线程在纯计算场景下无法并行。理解GIL的工作原理,能够帮助工程师正确区分CPU密集型与IO密集型任务:前者更适合多进程或C扩展,后者则可通过多线程或协程获得显著收益。在实际工程中,结合任务类型、数据规模、开发效率和部署环境进行并发选型,才能最大限度地发挥Python的性能潜力。本文通过

weixin_29597551的博客 133

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

安卓android!通宵都要看完这个Android关键技术点,赶紧收藏!

1、项目介绍 Flutter是目前比较流行的跨平台开发技术,凭借其出色的性能获得很多前端技术爱好者的关注,比如阿里闲鱼,美团,腾讯等大公司都有投入相关案例生产使用。 基于Flutter+Dart+chewie+photo_view+image_picker等技术开发的跨平台仿微信app聊天界面应用,实现了消息/表情发送、图片预览、长按菜单、红包/小视频/朋友圈等功能。 为何现在Android应用开发这么难找到工作? 随着资本的冷却,整个互联网市场也逐渐的冷静下来.Android应用开发从一开始能说几个四大

398

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

要明白的知识点

刚开始面试的几家公司,就是备受各种打击、就是一顿狂问,结果答上来的问题没几个,开始给自己的信心造成了一定的影响,后来经过调整后,又恢复到了信心满满的状态。一方面是工作经验有限,另一方面是没有进行系统的复习。后者占主要地位。所以如何复习好技术要点是找到好工作的必要前提(但也有很大一部分的运气成分在里面)。对于应届生而言,由于在校能学的技术有限,所以在开始面试完几家公司后,感觉备受打击是很正常的,这时...

dnl12138 373

Python多线程为何越跑越慢?一文讲透GIL与并发选型

从CPU密集型任务多线程反而更慢的常见困惑切入,引出CPython解释器中的全局解释器锁GIL。GIL通过互斥锁保证引用计数安全,却导致同一时刻仅一个线程执行字节码,多核并行受限。理解GIL的切换机制与IO操作主动释放锁的行为,是正确选择并发方案的前提。结合实测数据,对比单线程、多线程与多进程在CPU密集和IO密集场景下的性能差异,并给出根据任务画像选择threading、multiprocessing或asyncio的工程建议,帮助开发者避开“多线程一定加速”的误区,有效提升代码效率。

weixin_34214500的博客 445

今晚我们通宵学习SpringCloud

前言 该文档在Github上收获40K+star的Java面试神技(这赞数,质量多高就不用我多说了吧)非常全面,包涵Java基础、Java集合、JavaWeb、Java异常、OOP、IO与NIO、反射、注解、多线程、JVM、MySQL、MongoDB、Spring全家桶、计算机网络、分布式架构、Redis、Linux、git、前端、算法与数据结构、MyBatis、RocketMQ、Netty、Dubbo。内容非常丰富,已经帮很多人拿下互联网一线公司的offer。 每个知识点都有左侧导航书签页,看的时候十分

程序员环西 79

从多进程到epoll:Linux高并发服务器IO模型演进之路

高并发的本质,是操作系统在有限资源下对海量连接的高效调度。理解阻塞与非阻塞IO的差异,是掌握高并发服务端编程的前提。早期的多进程模型通过空间换并发,多线程模型通过共享提升效率,但连接数突破万级后,传统IO模型遭遇瓶颈,这就是C10K问题的根源。多路IO转接机制应运而生:select和poll解决了部分问题,却依然受限于全量线性扫描;epoll通过红黑树与就绪链表的事件驱动设计,将复杂度降到O(1),成为现代高性能服务器的基石。从LT/ET触发模式的选择,到Reactor与线程池的组合,再到实际压测调优,ep

weixin_33393017的博客 145
上一篇: lxml大战B站-从未见过如此丝滑的解析神器!
下一篇: 爬虫程序又莫名崩溃了?教你这一招轻松解决!
jspython
博客等级 码龄7年 61粉丝 109原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值