当前位置: 首页 > news >正文

【python高级用法】线程

前言

Python通过标准库的 threading 模块来管理线程。这个模块提供了很多不错的特性,让线程变得无比简单。实际上,线程模块提供了几种同时运行的机制,实现起来非常简单。

线程模块

  • 线程对象
  • Lock对象
  • RLock对象
  • 信号对象
  • 条件对象
  • 事件对象

 简单调用方式

class threading.Thread(group=None,target=None,name=None,args=(),kwargs={})
  • group: 一般设置为 None ,这是为以后的一些特性预留的
  • target: 当线程启动的时候要执行的函数
  • name: 线程的名字,默认会分配一个唯一名字 Thread-N
  • args: 传递给 target 的参数,要使用tuple类型
  • kwargs: 同上,使用字典类型dict

 创建线程的方法非常实用,通过`target`参数、`arg`和`kwarg`告诉线程应该做什么。下面这个例子传递一个数字给线程(这个数字正好等于线程号码),目标函数会打印出这个数字。

import threadingdef function(i):print ("function called by thread %i\n" % i)returnthreads = []for i in range(5):t = threading.Thread(target=function , args=(i, ))threads.append(t)t.start()
for i in range(5):t.join()

 线程被创建之后并不会马上运行,需要手动调用 start() , join() 让调用它的线程一直等待直到执行结束(即阻塞调用它的主线程, t 线程执行结束,主线程才会继续执行)

 实现一个线程

使用threading模块实现一个新的线程,需要下面3步:

  • 定义一个 Thread 类的子类
  • 重写 __init__(self [,args]) 方法,可以添加额外的参数
  • 最后,需要重写 run(self, [,args]) 方法来实现线程要做的事情

 当你创建了新的 Thread 子类的时候,你可以实例化这个类,调用 start() 方法来启动它。线程启动之后将会执行 run() 方法。

为了在子类中实现线程,我们定义了 myThread 类。其中有两个方法需要手动实现:

import threading
import timeexitFlag = 0class myThread (threading.Thread):def __init__(self, threadID, name, counter):threading.Thread.__init__(self)self.threadID = threadIDself.name = nameself.counter = counterdef run(self):print("Starting " + self.name)print_time(self.name, self.counter, 5)print("Exiting " + self.name)def print_time(threadName, delay, counter):while counter:if exitFlag:# 译者注:原书中使用的thread,但是Python3中已经不能使用thread,以_thread取代,因此应该# import _thread# _thread.exit()thread.exit()time.sleep(delay)print("%s: %s" % (threadName, time.ctime(time.time())))counter -= 1# Create new threads
thread1 = myThread(1, "Thread-1", 1)
thread2 = myThread(2, "Thread-2", 2)# Start new Threads
thread1.start()
thread2.start()# 以下两行为译者添加,如果要获得和图片相同的结果,
# 下面两行是必须的。疑似原作者的疏漏
thread1.join()
thread2.join()
print("Exiting Main Thread")

threading 模块是创建和管理线程的首选形式。每一个线程都通过一个继承 Thread 类,重写 run() 方法来实现逻辑,这个方法是线程的入口。在主程序中,我们创建了多个 myThread 的类型实例,然后执行 start() 方法启动它们。调用 Thread.__init__ 构造器方法是必须的,通过它我们可以给线程定义一些名字或分组之类的属性。调用 start() 之后线程变为活跃状态,并且持续直到 run() 结束,或者中间出现异常。所有的线程都执行完成之后,程序结束。

join() 命令控制主线程的终止。

Lock用法

两个或以上对共享内存的操作发生在并发线程中,并且至少有一个可以改变数据,又没有同步机制的条件下,就会产生竞争条件,可能会导致执行无效代码、bug、或异常行为。

竞争条件最简单的解决方法是使用锁。锁的操作非常简单,当一个线程需要访问部分共享内存时,它必须先获得锁才能访问。此线程对这部分共享资源使用完成之后,该线程必须释放锁,然后其他线程就可以拿到这个锁并访问这部分资源了。

很显然,避免竞争条件出现是非常重要的,所以我们要保证,在同一时刻只有一个线程允许访问共享内存。

然而,在实际使用的过程中,我们发现这个方法经常会导致一种糟糕的死锁现象。当不同的线程要求得到一个锁时,死锁就会发生,这时程序不可能继续执行,因为它们互相拿着对方需要的锁。

 为了简化问题,我们设有两个并发的线程( 线程A 和 线程B ),需要 资源1 和 资源2 .假设 线程A 需要 资源1 , 线程B 需要 资源2 .在这种情况下,两个线程都使用各自的锁,目前为止没有冲突。现在假设,在双方释放锁之前, 线程A 需要 资源2 的锁, 线程B 需要 资源1 的锁,没有资源线程不会继续执行。鉴于目前两个资源的锁都是被占用的,而且在对方的锁释放之前都处于等待且不释放锁的状态。

# -*- coding: utf-8 -*-import threadingshared_resource_with_lock = 0
shared_resource_with_no_lock = 0
COUNT = 100000
shared_resource_lock = threading.Lock()# 有锁的情况
def increment_with_lock():global shared_resource_with_lockfor i in range(COUNT):shared_resource_lock.acquire()shared_resource_with_lock += 1shared_resource_lock.release()def decrement_with_lock():global shared_resource_with_lockfor i in range(COUNT):shared_resource_lock.acquire()shared_resource_with_lock -= 1shared_resource_lock.release()# 没有锁的情况
def increment_without_lock():global shared_resource_with_no_lockfor i in range(COUNT):shared_resource_with_no_lock += 1def decrement_without_lock():global shared_resource_with_no_lockfor i in range(COUNT):shared_resource_with_no_lock -= 1if __name__ == "__main__":t1 = threading.Thread(target=increment_with_lock)t2 = threading.Thread(target=decrement_with_lock)t3 = threading.Thread(target=increment_without_lock)t4 = threading.Thread(target=decrement_without_lock)t1.start()t2.start()t3.start()t4.start()t1.join()t2.join()t3.join()t4.join()print ("the value of shared variable with lock management is %s" % shared_resource_with_lock)print ("the value of shared variable with race condition is %s" % shared_resource_with_no_lock)

 RLock用法

这种锁对比Lock有是三个特点:

1. 谁拿到谁释放。如果线程A拿到锁,线程B无法释放这个锁,只有A可以释放;

2. 同一线程可以多次拿到该锁,即可以acquire多次;

3. acquire多少次就必须release多少次,只有最后一次release才能改变RLock的状态为unlocked)

import threading
import timeclass Box(object):lock = threading.RLock()def __init__(self):self.total_items = 0def execute(self, n):Box.lock.acquire()self.total_items += nBox.lock.release()def add(self):Box.lock.acquire()self.execute(1)Box.lock.release()def remove(self):Box.lock.acquire()self.execute(-1)Box.lock.release()## These two functions run n in separate
## threads and call the Box's methods
def adder(box, items):while items > 0:print("adding 1 item in the box")box.add()time.sleep(1)items -= 1def remover(box, items):while items > 0:print("removing 1 item in the box")box.remove()time.sleep(1)items -= 1## the main program build some
## threads and make sure it works
if __name__ == "__main__":items = 5print("putting %s items in the box " % items)box = Box()t1 = threading.Thread(target=adder, args=(box, items))t2 = threading.Thread(target=remover, args=(box, items))t1.start()t2.start()t1.join()t2.join()print("%s items still remain in the box " % box.total_items)

 信号量用法

 信号量是由操作系统管理的一种抽象数据类型,用于在多线程中同步对共享资源的使用。本质上说,信号量是一个内部数据,用于标明当前的共享资源可以有多少并发读取。

在threading模块中,信号量的操作有两个函数,即 acquire() 和 release() 

  • 每当线程想要读取关联了信号量的共享资源时,必须调用 acquire() ,此操作减少信号量的内部变量, 如果此变量的值非负,那么分配该资源的权限。如果是负值,那么线程被挂起,直到有其他的线程释放资源。
  • 当线程不再需要该共享资源,必须通过 release() 释放。这样,信号量的内部变量增加,在信号量等待队列中排在最前面的线程会拿到共享资源的权限。

 存在的问题:

假设有两个并发的线程,都在等待一个信号量,目前信号量的内部值为1。假设第线程A将信号量的值从1减到0,这时候控制权切换到了线程B,线程B将信号量的值从0减到-1,并且在这里被挂起等待,这时控制权回到线程A,信号量已经成为了负值,于是第一个线程也在等待。

# -*- coding: utf-8 -*-"""Using a Semaphore to synchronize threads"""
import threading
import time
import random# The optional argument gives the initial value for the internal
# counter;
# it defaults to 1.
# If the value given is less than 0, ValueError is raised.
semaphore = threading.Semaphore(0)def consumer():print("consumer is waiting.")# Acquire a semaphoresemaphore.acquire()# The consumer have access to the shared resourceprint("Consumer notify : consumed item number %s " % item)def producer():global itemtime.sleep(10)# create a random itemitem = random.randint(0, 1000)print("producer notify : produced item number %s" % item)# Release a semaphore, incrementing the internal counter by one.# When it is zero on entry and another thread is waiting for it# to become larger than zero again, wake up that thread.semaphore.release()if __name__ == '__main__':for i in range (0,5) :t1 = threading.Thread(target=producer)t2 = threading.Thread(target=consumer)t1.start()t2.start()t1.join()t2.join()print("program terminated")

 queue线程通信

Python的threading模块提供了很多同步原语,包括信号量,条件变量,事件和锁。如果可以使用这些原语的话,应该优先考虑使用这些,而不是使用queue(队列)模块。队列操作起来更容易,也使多线程编程更安全,因为队列可以将资源的使用通过单线程进行完全控制,并且允许使用更加整洁和可读性更高的设计模式。 

 

Queue常用的方法有以下四个:

  • put(): 往queue中放一个item
  • get(): 从queue删除一个item,并返回删除的这个item
  • task_done(): 每次item被处理的时候需要调用这个方法
  • join(): 所有item都被处理之前一直阻塞

 

from threading import Thread, Event
from queue import Queue
import time
import random
class producer(Thread):def __init__(self, queue):Thread.__init__(self)self.queue = queuedef run(self) :for i in range(10):item = random.randint(0, 256)self.queue.put(item)print('Producer notify: item N° %d appended to queue by %s' % (item, self.name))time.sleep(1)class consumer(Thread):def __init__(self, queue):Thread.__init__(self)self.queue = queuedef run(self):while True:item = self.queue.get()print('Consumer notify : %d popped from queue by %s' % (item, self.name))self.queue.task_done()if __name__ == '__main__':queue = Queue()t1 = producer(queue)t2 = consumer(queue)t3 = consumer(queue)t4 = consumer(queue)t1.start()t2.start()t3.start()t4.start()t1.join()t2.join()t3.join()t4.join()

 

生产者使用 Queue.put(item [,block[, timeout]]) 来往queue中插入数据。Queue是同步的,在插入数据之前内部有一个内置的锁机制。

可能发生两种情况:

  • 如果 block 为 True , timeout 为 None (这也是默认的选项,本例中使用默认选项),那么可能会阻塞掉,直到出现可用的位置。如果 timeout 是正整数,那么阻塞直到这个时间,就会抛出一个异常。
  • 如果 block 为 False ,如果队列有闲置那么会立即插入,否则就立即抛出异常( timeout 将会被忽略)。本例中, put() 检查队列是否已满,然后调用 wait() 开始等待。

消费者从队列中取出整数然后用 task_done() 方法将其标为任务已处理。

消费者使用 Queue.get([block[, timeout]]) 从队列中取回数据,queue内部也会经过锁的处理。如果队列为空,消费者阻塞。

 

参考链接:

Python并行编程 中文版 — python-parallel-programming-cookbook-cn 1.0 文档

相关文章:

【python高级用法】线程

前言 Python通过标准库的 threading 模块来管理线程。这个模块提供了很多不错的特性,让线程变得无比简单。实际上,线程模块提供了几种同时运行的机制,实现起来非常简单。 线程模块 线程对象Lock对象RLock对象信号对象条件对象事件对象 简单…...

分布式高级知识点

分布式一致性算法: Paxos Paxos 是一种分布式一致性算法,用于在分布式系统中达成共识。它可以保证,即使在存在节点故障的情况下,系统也能就某个值达成一致。 Paxos 算法的基本思想是,首先选出一个协调者(leader)。协调者负责向其他节点发送提案(proposal)。其他节点收…...

Linux 命令之 dpkg 的简单使用

查询已安装的软件包及其依赖关系 dpkg -s name...

Ubuntu20.04服务器使用教程(安装教程、常用命令、故障排查)持续更新中.....

安装教程(系统、NVIDIA驱动、CUDA、CUDNN、Pytorch、Timeshift、ToDesk) 制作U盘启动盘,并安装系统 在MSDN i tell you下载Ubuntu20.04 Desktop 版本,并使用Rufus制作UEFI启动盘,参考UEFI安装Ubuntu使用GPTUEFI模式安…...

访问学者J1签证的申请流程

访问学者J1签证是许多人前往美国进行学术研究和文化交流的重要途径之一。申请J1签证需要经过一系列步骤和程序,让知识人网小编带大家来了解一下申请流程吧。 首先,申请者需要确认自己符合J1签证的资格要求。这包括被美国的赞助机构或组织接受&#xff0c…...

51单片机(STC8)-- GPIO输入输出

文章目录 I/O口相关寄存器端口数据寄存器端口模式配置寄存器(PxM0,PxM1)端口上拉电阻控制寄存器(PxPU)关于I/O的注意事项 配置I/O口I/O设置demoI/O端口模式LED控制(I/O输出)按键检测(I/O输入) S…...

【实用安装教程】在win系统下制作Mac OS镜像启动U盘

第一步:制作Mac OS系统引导镜像启动U盘 准备一个8G(或以上)的U盘插入到win系统的电脑上 去下载TransMac(Mac启动盘制作工具)v10.4按照说明安装好 插入准备好的U盘,U盘数据要转移,打开TransMac,右键U盘选…...

职场唠嗑-国家教学

文章目录 职场晋升潜规则:让上司看到自己工作能力职场生存指南:脆弱无罪,眼泪如何变利器关于具备谋取好职位的资格实习生在职场的基本“规矩”比能力更能决定人生的,是你对工作的态度跳槽:看别人家的“饭”端自家的碗职…...

【温故而知新】JavaScript数据结构详解

一、概念 JavaScript是一种弱类型的编程语言,它提供了一些内置的数据结构来存储和组织数据。 在计算机科学中,数据结构是一种特定的方式来组织和存储数据,以便于有效地访问和修改数据。在JavaScript中,数据结构是指相互之间存在…...

matlab如何标定相机内外参和畸变参数

关于内外参矩阵和畸变矩阵可以学习 https://blog.csdn.net/qq_30815237/article/details/87530011?spm1001.2014.3001.5506 在APP中找到 camera Calibrator 点击 Add Images,导入拍照图片。标定20张左右就够了,然后角度变一下,但不需要变太…...

【卫星科普】什么是农业一号卫星和农业二号卫星?

农业一号卫星和农业二号卫星是中国自主研发的两颗重要卫星,主要用于农业领域的监测和研究。 农业一号卫星是中国第一颗具备红边波段传感器的卫星,也是世界上第一颗具备红边波段的宽视场多光谱中高分辨率卫星。这对农业农村遥感监测非常重要,…...

imgaug库指南(一):从入门到精通的【图像增强】之旅

文章目录 引言imgaug简介安装和导入imgaug代码示例imgaug的强大之处和用途小结结尾 引言 在深度学习和计算机视觉的世界里,数据是模型训练的基石,其质量与数量直接影响着模型的性能。然而,获取大量高质量的标注数据往往需要耗费大量的时间和…...

vue封装基础input组件(添加防抖功能)

先看一下效果&#xff1a; // 调用页面 <template><div><!-- v-model&#xff1a;伪双向绑定 --><my-input v-model"inputVal" label"姓名" type"textarea" /></div> </template><script> import…...

小程序一次性订阅消息(消息通知):java服务端实现

文章目录 引言一、消息订阅1.1 小程序订阅消息功能介绍1.2 消息分类1.2.1 新版一次性订阅消息Beta1.2.2 一次性订阅消息&#xff08;用户通过弹窗订阅&#xff09;1.2.3 长期订阅消息&#xff08;用户通过弹窗订阅&#xff09;1.2.4 设备订阅消息 二、获取模板ID1.登录[微信公众…...

百度自由DIY小程序源码:PHP+MySQL组合开发 带完整的搭建教程

随着移动互联网的快速发展&#xff0c;小程序已成为企业与用户互动的重要平台。然而&#xff0c;对于许多中小企业和开发者来说&#xff0c;从零开始开发一款小程序需要投入大量的时间和资源。 以下是部分代码示例&#xff1a; 系统特色功能一览&#xff1a; 1.高度自定义&…...

Vue中的选项式 API 和组合式 API,两者有什么区别

Vue中的选项式 API&#xff08;Option API&#xff09;和组合式 API&#xff08;Composition API&#xff09;是两种不同的组件编写方式&#xff0c;它们各有特点和适用场景&#xff1a; 选项式 API&#xff08;Option API&#xff09;: 传统方法&#xff1a;Vue最初的编程范式…...

Linux下误删除后的恢复操作测试之extundelete工具使用

一、工具介绍 extundelete命令的功能可用于系统删除文件的恢复。在使用前&#xff0c;需要先将要恢复的分区卸载&#xff0c;以防数据被意外覆盖。 语法格式&#xff1a;extundelete [参数] 文件或目录名 常用参数&#xff1a; --after 只恢复指定时间后被删除的文件 --bef…...

table表格中使用el-popover 无效问题解决

实例只针对单个的按钮管用在表格里每一列都有el-popover相当于是v-for遍历了 所以我们在触发按钮的时候并不是单个的触发某一个 主要执行 代码 <el-popover placement"left" :ref"popover-${scope.$index}"> 动态绑定了ref 关闭弹窗 执行deltask…...

c++类全面讲解

文章目录 前言类的基本概念基本结构类与结构体的区别示例代码 类的属性和方法属性&#xff08;成员变量&#xff09;方法&#xff08;成员函数&#xff09;访问修饰符示例代码 类的构造函数和析构函数构造函数析构函数示例代码 类的构造函数重载重载构造函数示例代码 类中的拷贝…...

使用Python和Pygame库创建简单的的彩球效果

简介 Pygame是一款强大的游戏开发库&#xff0c;可以用于创建各种有趣的图形效果。为了更好地了解Pygame的功能&#xff0c;今天我们将要做的是在屏幕上随机生成一些彩色的小球&#xff0c;并使它们以不同的速度和方向移动。当小球碰到屏幕边缘时&#xff0c;它们将反弹。 功能…...

第2课 使用FFmpeg读取rtmp流并用openCV显示视频

本课对应源文件下载链接&#xff1a; https://download.csdn.net/download/XiBuQiuChong/88680079 这节课我们开始利用ffmpeg和opencv来实现一个rtmp播放器。播放器的最基本功能其实就两个:显示画面和播放声音。在实现这两个功能前&#xff0c;我们需要先用ffmpeg连接到rtmp服…...

【中小型企业网络实战案例 七】配置限速

相关学习文章&#xff1a; 【中小型企业网络实战案例 一】规划、需求和基本配置 【中小型企业网络实战案例 二】配置网络互连互通【中小型企业网络实战案例 三】配置DHCP动态分配地址 【中小型企业网络实战案例 四】配置OSPF动态路由协议【中小型企业网络实战案例 五】配置可…...

Hive实战:实现数据去重

文章目录 一、实战概述二、提出任务三、完成任务&#xff08;一&#xff09;准备数据1、在虚拟机上创建文本文件2、上传文件到HDFS指定目录 &#xff08;二&#xff09;实现步骤1、启动Hive Metastore服务2、启动Hive客户端3、基于HDFS数据文件创建Hive外部表4、利用Hive SQL实…...

客户满意度调查常用的ChatGPT通用提示词模板

调查目的与范围&#xff1a;如何明确调查的目的和范围&#xff0c;确保调查的针对性&#xff1f; 调查方法选择&#xff1a;如何选择合适的调查方法&#xff0c;如问卷调查、访谈等&#xff1f; 问卷设计&#xff1a;如何设计问卷&#xff0c;确保问题的针对性和客观性&#…...

Android--Jetpack--Paging详解

不尝世间醋与墨&#xff0c;怎知人间酸与苦。 择一业谋食养命&#xff0c;等一运扭转乾坤。 你见过哪些令你膛目结舌的代码技巧&#xff1f; 文章目录 不尝世间醋与墨&#xff0c;怎知人间酸与苦。择一业谋食养命&#xff0c;等一运扭转乾坤。你见过哪些令你膛目结舌的代码技…...

Unity 基于UDP实现本地时间与网络时间校验 防客户端修改日期作弊

新建一个Unity GameObject 挂上NTPComponent脚本 时间校验 源码 using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using UnityEngine.Networking; using System.Text; using System.Net.Sockets; using System.Net; using Sys…...

ArduPilot开源代码之MatekSys Optical Flow 3901-L0X

ArduPilot开源代码之MatekSys Optical Flow 3901-L0X 1. 源由2. 安装3. 参数配置3.1 配置光流定位3.2 配置激光测距3.3 辅助配置 4. 测试4.1 光流数据测试4.2 测距数据测试4.3 飞行注意事项4.4 实际飞行测试 5. 参考资料 1. 源由 之前介绍过MatekSys Optical Flow 3901-L0X模块…...

【时钟】分布式时钟HLC|Logical Time|Vector Clock|True Time

目录 简略 详细 附录 1 分布式系统不能使用NTP的原因 简略 分布式系统中不同于单机系统不能使用NTP(网络时间协议&#xff08;Network Time Protocol&#xff09;)来获取时间&#xff0c;所以我们需要一个特别的方式来获取分布式系统中的时间&#xff0c;mvcc也是使用time保证读…...

人工智能AI与3D视觉技术的结合正在引领新一代移动机器人的革新

随着科技的飞速发展&#xff0c;人工智能AI与3D视觉技术的结合正在引领新一代移动机器人的革新。富唯智能移动机器人&#xff0c;以其独特的3D视觉技术&#xff0c;赋予了移动机器人一双“智慧之眼”&#xff0c;从而为现代工业自动化带来了前所未有的突破。 富唯智能移动机器…...

NSSCTF 简单包含

开启环境: 使用POST传flag&#xff0c;flag目录/var/www/html/flag.php 先使用post来尝试读取该flag.php 没反应: 查看一下源码index.php&#xff0c;看有什么条件 base64解密: <?php$path $_POST["flag"];if (strlen(file_get_contents(php://input)) <…...

添加网站图标/今日新闻最新头条

叨叨两句 昨天爬了一下午坑才出来的我向大家问好?&#xff0c;要说小程序基础库都1.9了&#xff0c;但是坑还是很多。一方面是由于小程序的文档不是太友好&#xff0c;也许某个地方告诉你了&#xff0c;但是不是那么 容易发现。另一方面&#xff0c;微信大佬手握9亿多用户&…...

网站后台功能需求/目前在哪个平台做推广好

科学研究的目的是通过观察、实验和思考来更好地理解我们周围的世界&#xff0c;从而改进我们的生活和社会。科学研究可以帮助我们解决许多关键问题&#xff0c;比如改善医疗和公共卫生、保护环境、提高农业生产率、发明新技术和产品等。 基础研究是科学研究的一种&#xff0c;它…...

wordpress搜索功能加强/直播回放老卡怎么回事

PHP文章摘要生成方法(函数)文章生成摘要的方法有多种&#xff0c;可以用JS在客户端生成&#xff0c;也可以在服务器端生成&#xff0c;当然更不排除在数据库中加一个摘要字段&#xff0c;在发布文章的时候自行设置。以下是在服务器端生成时的方法。我们在写BLOG时经常需要显示文…...

微网站怎么自己做/销售怎么做

为了最大程度地减少USB的功耗&#xff0c;整个USB核心需要实现在DesignWare USB 2.0 nanoPHY中所采用的各种功耗节省策略。Synopsys已经在DesignWare高速USB 2.0 On-the-Go控制器&#xff08;HS OTG&#xff09;核心中实现了这些策略以及其它能够理想地适用于电池供电应用的策略…...

山西做网站/代写企业软文

我的Docker专栏 https://blog.csdn.net/weixin_45580378/category_12276045.html docker 镜像 https://registry.hub.docker.com/r/nacos/nacos-server/tags 1.下载nacos镜像 这里下载的是2.0.3 docker pull nacos/nacos-server:2.0.32.查看镜像是否下载成功 如下图 docker…...

深圳网站建设工作室/周口网站seo

做 Java 开发的你&#xff0c;一天是不是这样度过的&#xff1f;8:00--9:30 闹铃响了N遍后&#xff0c;匆忙起床洗漱&#xff0c;在拥挤的地铁上刷朋友圈、公众号和技术论坛9:30--10:00 到公司&#xff0c;吃早点&#xff0c;打开电脑收邮件&#xff0c;终终终于准备好状态开始…...