TOC

并发编程

    并发编程主要是解决多个事件需要同时处理的问题,我们经常会听到高并发这种字眼,在正式说并发之前需要先了解什么是并行( parallel ),什么是并发( concurrency ),并行指的是,可以互不干扰的在同一时刻同时做多件事,并发也是同时做某些事,但是并发强调的是一个时段内要做什么事;
    并发和并行的区别就是一个处理器同时处理多个任务和多个处理器或者是多核的处理器同时处理多个不同的任务,并发是逻辑上的同时发生(simultaneous),而并行是物理上的同时发生;
    并发(concurrency),又称共行性,是指能处理多个同时性活动的能力,并发事件之间不一定要在同一时刻发生,并发要求在一定的时间范围内;
    并行(parallelism),是指在同一时间同时发生的多个并发事件,具有并发的含义,而并发则不一定并行,并行要求在同一时刻,实际上,并行也就是并发的一种处理方案而已;
    我们可以理解为高速公路上的四个个车道,在同一个时间点上在四个车道上有四辆车在行驶,这说的就是并行,而并发指的是,在这一个小时内,通过这个路段有三千辆车需要通过,这说的是并发,而且还是高并发,并发指的是一段时间内,这一段时间是多长,有用户自定义,所以并发指的是,在一个限定的时间内,处理了多少事情,而并行指的是在一个时间点,同时处理了多少事情;

并发解决方案

    高并发的解决,是互联网分布式系统架构设计中必须考虑的因素之一,它通常是指,通过设计保证系统能够同时处理很多请求,如在同一时间,有大量用户同时访问同一个网站,这样就很容易导致服务器和数据库资源被占满崩溃,甚至导致数据库的存储和更新结果跟理想不一致,出现重复的数据记录,多次添加记录导致数据错乱等各种问题;
    为了解决高并发带来的各种问题,在开发人员一侧,到目前位置,也有很多的解决方案,如队列、争抢、预处理、并行、提速以及消息中间件,等类型等方案;

队列

    队列也是目前解决高并发最常见的手段之一,队列我们往往会称其为Buffer,即缓冲地带,它就是将任务排列成一个队列,这个队列类似一个管道,具有先进先出的概念,先进入这个管道的任务,先被执行,未被执行等,就暂存在这个管道之中,等待前面等任务一一执行;
    当然,队列这种先进先出的思想,在某些场景下可能也有一定的问题,比如有一个管理系统,这个管理系统每到早上8点会给所有用户发送一条每日新闻,但是由于用户量比较大,可能所有消息发完需要一个小时,也就是9点才能发完,但是8:30这个时候,网站需要发一条紧急新闻,通知一个重要的消息给所有用户,那么这个时候,这种先进先出的策略可能就不怎么符合要求了;
    所有,就有了优先队列的概念,我们还可以给队列设置权重,这个权重较高的任务先执行,其他队列让出CPU,使这个有先队列先消费,其他队列按照先进先出的逻辑执行,除了这个带权重的队列之外,这就是所谓的优先队列;

争抢

    在解决高并发的方案之中也有一种争抢的方案,争抢方案中还有一个锁的概念,它也是为了解决争抢带来的资源竞争的问题,争抢方案实际就是,目前有一个单核CPU的服务器,但是有100个任务需要在这个服务器上去运行,对于争抢方案来说就是,100个任务,同时去抢这个CPU的时间片,一旦某一个任务抢到了之后,就给这个CPU开启一个锁,即独占锁也称之为排它锁,使得其他的任务只能等待。
    这个独占锁也就是为了解决资源争用的问题,因为如果进程10抢到了CPU的时间片,然后开始进行运算,但是在进程10还没执行完之前,CPU的时间片又被其他的进程抢走了,所以,就借助独占锁来实现独占的实现;
    争抢这种方式看起来貌似还不错,但是仔细想想,也存在很多问题,比如在高并发的场景下,有可能有一些进程始终都抢不到CPU,一直都不被处理,就会出现这种现象,但它也是一种解决方案,并且也有不少但领域在使用;

预处理

    之所以需要使用队列、争抢以及其他概念来处理高并发,其主要原因之一是在于数据的处理得太慢了,比如一个数据库程序,一下子来了几千个查询请求,数据库得一个一个的去进行SQL分析、SQL优化、数据遍历等等一系列等流程之后才能处理完成一个流程,这就很慢了;
    所以我们就需要将一些热点数据提前准备好,比如我们可以将查询次数较多的表,或者热数据,进行预先处理,比如将这些数据的提前查询好,将结果放到数据库查询缓存区里面,做预处理,提前将数据放到缓存之中准备好,这样就可以大大的减少客户端等待的时间,省去了很多的SQL引擎优化、数据遍历等流程,这也是一种比较好的高并发的解决方案,这就不是Buffer了,而是Cache,往往都是K/V结构,快速定位;

并行

    并行指的同一时间,同时做多件事情,比如现在有一个进程来处理数据,这样显然很慢,那么我们如果在一个多核CPU的服务器下,使用多个进程同时来处理数据,那这样的话,我们就可以在同一个时刻多个进程同时执行多个任务,这样就是并行处理;
    当然,并行也可以这样理解,我们可以利用多个服务器,在同一个时刻同时执行多个任务,这样也是并行处理;

提速

    提速是一种垂直思想,提升硬件的配置,说白了就是提高单核心CPU的能力,或者说给单个服务器配备更多的CPU核心数,或者提升CPU的频率,但是这样是有天花板的,而且成本也会非常的大,这也是一种方法;

消息中间件

    消息中间件也是目前解决高并发很流行的一种方式,只不过它的角色可能和上面有点不通,它位于两个系统之间。主要承担消息泄洪的作用,在高并发的情况下,大量的请求涌入进来,你会发现出现各种网络拥塞现状,系统连周转的余地都没有了,在这种情况之下,应该新增一个队列,此队列非彼队列,此队列为系统外的队列,这个队列是我们为了系统和系统之间进行数据缓冲的队列,这种队列被称之为第三方队列;
    这种第三方队列,我们称之为消息中间件,它们也被称之为消息队列,它们往往用于系统之间用来做数据缓冲的,因为请求太多了,这些消息中间件比较常见的有Kafka、RabbitMQ、RocketMQ等;

进程和线程

    其实每个操作系统里面都有一个子系统,即进程管理子系统,而目前大家所使用到的系统不但有进程,在进程里面还可以创建线程,但是也不能说所有的操作系统都有线程的概念,因为计算机起源的最初,是没有这个线程最小之下单元的概念的,但是在目前,在21世纪下,人们所使用的操作系统基本上具备了进程和线程的概念;

进程

     一个系统上面那么多进程,内核为了实现管理每一个进程,到底在某一个时刻谁到CPU上去运行呢、轮到它运行那么运行多长时间、如果要提前保存线程,那该如何做记录等,这都是由内核来管理,内核必须去追踪每个进程的进程状态信息,而且它必须要明确知道当前系统上一共启动了多少个进程了。其实我们的内核在内核空间当中自己维护着有一个数据结构,这个结构叫做task structure(任务结构),记录着每一个进程叫什么名字,这个进程的PID号是多少,它分配的内存大小是多少,它执行了哪些命令了,上一次执行到哪儿了,等等等等.......
    这些数据内核都有做保存,而这些数据都保存着在这个数据结构里面,内核为每一个进程都保存一个数据结构,简单来讲一个进程100k如果我们当前系统上运行100个进程那么就需要100*100k大小的内核空间,来保存数据结构,当需要恢复线程的时候,CPU就把要恢复这个进程的数据结构都从内核空间读到CPU的寄存器上来,而后继续上一次运行;

线程

    而对于线程,我们可以在并行编程模型下,把一个进程内部给它分成N个小的执行实体,每个实体都有自己的指令和数据,这样就可以分为N个执行流,这N个执行流彼此之间是不相交叉的,一个执行流在1号CPU上运行,第二个执行流在2号CPU上运行以此类推。这就是把一个进程在内部划分成了多个小实体,这些进程内部的执行单元比进程更小更加精细,而这每一个单位都可以单独被CPU执行,所以CPU以后执行的不在是进程,而是这个小单位,这个单位叫做线程。
    假如我们有个数据库,数据背后有多个数据文件,如果我们要让别人查询的话,那就说明我们要把这个数据文件都打开(放到内存中去),那三个人使用三个进程的话,如果请求的是同一个数据文件,那就是访问一个文件需要打开三个独立隔离的进程,需要开辟三个独立隔离的内存空间,但是如果使用线程的话,就很好的解决了内存浪费问题,三个访问只需要打开一个就行了,因为进程间的数据是共享的。第一次使用的时候打开数据文件载入内存,第二次再次请求这个数据的时候只需要去内存提取就行了就不需要再次向内核请求打开文件了,所以线程比进程更节省空间;
    一般来说在实现了线程的操作系统之上,线程就是操作系统能够调用的最小的运算单元,它负责最终去执行各种计算的,线程它是包含在进程当中的,换句话说,进程实际上是线程的一个容器,由此可以看出线程实际上是由进程来管理的,是由进程来创建和销毁的,此时的进程更像是一个资源调度单元,负责来管理线程的各种事物;

总结

    进程是具有一定独立功能的,在计算机中已经运行的程序的实体。在早期系统中(如linux 2.4以前),进程是基本运作单位,在支持线程的系统中(如windows,linux2.6)中,线程才是基本的运作单位,而进程只是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。若干进程有可能与同一个程序相关系,且每个进程皆可以同步(循序)或异步(平行)的方式独立运行。现代计算机系统可在同一段时间内以进程的形式将多个程序加载到存储器中,并借由时间共享(或称时分复用),以在一个处理器上表现出同时(平行性)运行的感觉。同样的,使用多线程技术(多线程即每一个线程都代表一个进程内的一个独立执行上下文)的操作系统或计算机架构,同样程序的平行线程,可在多核CPU主机或网络上真正同时运行(在不同的CPU上)。
    线程也被称之为轻量级进程(Lightweight Process LWP),它是程序执行流的最小单元,一个标准的线程是由线程ID、当前指令指针(PC)、寄存器和堆栈组成,在操作系统之中,创建一个线程比创建一个进程要快10-100倍,可以看出线程是非常轻量的;
    在一个操作系统之上,每一个进程都认为自己独占该计算机的所有硬件资源,而线程就需要受到进程的限制,同一个进程内的所有线程都可以共享该进程内的所有资源,但每个线程依旧还是会拥有自己但独立但堆栈;

线程状态

    线程的状态变化和进程的状态变化差不多,它们都有几个共同都几个创建,如就绪、运行、阻塞和终止,不论是进程还是线程,首先会创建出来,创建出来之后,就进入就绪状态,就绪态这些线程操作系统就可以调度它,给它分配资源,如CPU资源,一旦将这个线程调度到CPU上,就可以执行这个线程内部到指令了;
    CPU的计算能力是非常强劲的,所以这就意味着它会浪费大量的时间在等待上,在计算机早起时进程到CPU上执行到顺序是一个一个来,但是后来慢慢发现这样做不太合适,后来就换成了一个进程只运行一段时,然后将其他进程切换到CPU上来运行,然后又回去继续运行原来没有运行完成的进程指令,以此循环;
    如果在运行的过程中,线程需要访问磁盘,或者访问网络IO,那么此时,就会进入阻塞状态,放弃了CPU的使用权,因为磁盘IO和网络IO都是非常非常慢的两种场景,本身分配给线程的CPU时间片就几纳秒,访问这种较慢的设备,那不CPU的时间全部浪费了,所以阻塞状态就会放弃CPU的使用权,并且连CPU调度的资格都没有,只有当这个线程重新进入就绪状态,才有可能重新获得CPU的时间片;
    就这样,CPU的整个工作按照时间段来划分,实际上到目前为止,还是这种模式,将CPU的整个时间片划分给各个进程,这其实就是一种虚拟化技术,将一个CPU当做个CPU来利用; 
创建(New):新创建的线程,尚未执行;
就绪(Ready):线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权;
运行(Running):可运行状态(runnable)的线程获得了cpu 时间片(timeslice) ,执行程序代码;
阻塞(Blocked):阻塞状态是指线程因为某种原因放弃了CPU使用权,比如该线程需要访问磁盘,也即让出了cpu timeslice,暂时停止运行。直到线程进入可运行(runnable)状态,才有机会再次获得cpu timeslice 转到运行(running)状态。阻塞的情况分三种;
终止(Terminated):线程run()、main() 方法执行结束,或者因异常退出了run()方法,则该线程结束生命周期。死亡的线程不可再次复生;

Python线程开发

    不管是Python开是其他开发语言,进程或线程都是系统提供的功能,并非开发语言提供的,创建一个线程都是操作系统线程,它们都需要调用操作系统的系统调用,才能创建出真正的线程来,进程靠线程执行指令,所以不论哪个开发语言所提供的多线程开发接口,都最少有一个线程,这个线程,我们称之为主线程,主线程将是第一个启动的线程, 并且,同一个进程中的所有线程,都可以共同使用这个进程的资源,因为进程间的数据是共享的;

多线程实现

    在Python中,多线程常用两大模块,_thread和threading,thread模块已被废弃。用户可以使用threading模块代替。为了兼容性,Python3 将 thread 重命名为 "_thread",threading 模块除了包含 _thread 模块中的所有方法外,还提供的其他方法如threading.currentThread、threading.enumerate、hreading.activeCount等,threading模块下的Thread类常用参数如下;
threading模块
    current_thread:返回当前线程对象;
    main_thread:返回主线程对象;
    active_count:当前处于active状态的线程个数;
    enumerate:返回所有活着的线程列表,不包括已终止和未开始的线程;
    get_ident:返回当前线程的ID,非0整数;
Thread类方法
    target:线程调用的对象,即目标函数;
    name:自定义线程名称;
    args:为目标函数传递实参,元组;
    kwargs:为目标函数传递实参,字典;
Thread实例方法
    start:调用操作系统的系统调用开辟一个新线程,并启动该线程,需要注意的是start方法一个线程最多只能被调用一次,如果大于一次,会抛出RuntimeError;
    run:运行线程方法,和start有点相像,start方法主要是用来创建并启动操作系统线程的,而run方法则是将target函数进行包装,并将该函数加入到start启动的线程中去运行;
    ident:返回线程ID,它是非0整数;
    name:线程名称,只是一个标识符,getName、setName可以获取和设置这个名称;
    is_active:返回线程是否存活;
    多线程是一种并行技术,不管它是真并行还是假并行,它主要就是解决并发问题,如下,就是一种并行技术,如下示例其实有两个线程,一个线程是主线程,还有一个线程是工作线程;
    需要注意的是,time.sleep会调用线程阻塞,让调用的线程进行阻塞状态,这里重点是调用的线程,只阻塞哪个调用time.sleep接口的线程;
def worker():
    print("I'm Workering")
    time.sleep(1)
    print("Finish")
thread1 = threading.Thread(target=worker, name="worker1")
thread1.start() # 启动线程
# I'm Workering
# Finish

线程退出

    Python没有提供线程退出的方法,那么在Python中只有两种情况才会导致线程退出,第一,线程函数指令执行完毕,线程生命周期结束,第二,线程函数中抛出异常,线程异常终止;
    对于抛出异常这个线程退出的情况,需要注意的是,线程出现异常,它并不会影响主线程,因为它们之间都是隔离的,只要主线程不崩溃,那么程序的退出码就是0,0代表该进程正常结束,非0则异常结束;
def worker():
    print("I'm Workering")
    raise EOFError
    print("Finish")
thread1 = threading.Thread(target=worker, name="worker-1")
thread1.start()
print(1)
# I'm Workering
# 1
# Exception in thread worker-1:
# Traceback (most recent call last):
#   File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/threading.py", line 914, in _bootstrap_inner
#     self.run()
#   File "/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/threading.py", line 862, in run
#     self._target(*self._args, **self._kwargs)
#   File "/usr/local/Project/cce/Py/edu.py", line 11, in worker
#     raise EOFError
# EOFError

# 可以看到,即使子线程抛出了EOFError异常,主线程还是打印出了1,也就表名了,子线程中的异常,不会导致主线程退出;
    在Python中的线程是没有优先级的,也没有线程组的概念,同时,它也不能显式的使其销毁、停止和挂起,所以也无法显式的恢复和中断了,其实也就一个操作,将对象拿过来直接start就行了;
  • 注意:需要注意的是,线程一旦结束,那么该结束的线程对象,将无法再次启动 ,每个线程只能启动一次;

线程传参

    线程传参数其实很简单,和函数传参一摸一样,其实它也就是函数传参,主要是使用args和kwargs参数来进行传参,如下;
def worker(n,m):
    print("I'm Workering")
    print("收到参数为:%s %s" %(n,m))
    print("Finish")
thread1 = threading.Thread(target=worker, name="worker-1",kwargs={'n':1,'m':2})  # 使用kwargs进行传参
thread1.start()
# I'm Workering
# 收到参数为:1 2
# Finish

线程并发

    线程并发,即多个线程同时并行处理,此案例为两个线程,一个为主线程,主线程主要负责监控子线程的状态,子线程来负责具体的工作;
def worker():
    print("I'm Workering")
    time.sleep(1)
    print("Finish")
thread1 = threading.Thread(target=worker, name="worker-1")
thread1.start()

# 主线程负责监控子线程,一旦子线程全部结束,主线程即退出,程序结束
while True:
    if threading.active_count() == 1: # 或使用 thread1.isAlive()
        break
print("程序结束")
# I'm Workering
# Finish
# 程序结束

Start和Run方法

    Start和Run这两个方法,看起来貌似一样的,我们通过重写threading.Thread类的这两个方法会发现,每启动一个线程,首先会运行start()方法,接着就会运行run()方法;
    但是我们通过重写threading.Thread类的方式可以发现,单独运行run()方法也可以调用工作函数的执行,那么既然两者都可以执行工作函数,为什么要设置start和run方法呢;
class MyThread(threading.Thread):
    def start(self):
        print("进入start阶段")
        super().start()
    def run(self):
        print("进入run阶段")
        super().run()

def worker():
    print("I'm Workering")

thread1 = MyThread(target=worker, name="worker-1")
thread1.start()
# 进入start阶段
# 进入run阶段
# I'm Workering
    其实主要原因是因为,start()方法,的主要作用是调用操作系统的系统调用,来创建一个线程,并且启动线程,然后隐式调用run()方法,也就是说,start()启动的这个线程在这个线程内部会调用run()方法的工作函数,而run()方法主要就是执行工作函数,所以执行start()方法实际上是一个新的子线程调用了run()方法的函数;
    而我们单独运行run(),则是直接在主线程中调用了工作函数的执行,此时就没有创建一个新的线程去执行这个工作函数,这就是两者的不同之处,如下示例;
def worker():
    print(threading.enumerate(),threading.current_thread())
    print("I'm Workering")
thread1 = threading.Thread(target=worker, name="worker-1")
thread1.start() # 调用start方法,会发现此时有两个线程,执行该工作函数是子线程,并非主线程
# [<_MainThread(MainThread, started 140734894869952)>, <Thread(worker-1, started 123145383366656)>] <Thread(worker-1, started 123145383366656)>
# I'm Workering

thread1.run()  # 调用run方法可以发现线程列表里面只有一个主线程,并没有创建一个新的线程,去执行这个工作函数
# [<_MainThread(MainThread, started 140734758628800)>] <_MainThread(MainThread, started 140734758628800)>
# I'm Workering

# 多线程每个线程仅能调用一次start()或者run()方法,所以两者并存会抛出异常,请交替测试

获取返回值

    一般来讲,让线程7x24小时运行着一般会太在意它执行完之后的返回值,一般来讲这种工作函数的返回值都不会使用return语句来返回,大多数情况下都是输出日志、网络传输其他子系统或者放入数据库,总之会将这个返回值给它传出去,而不是return出去;
    这个也是根据需求来定,但是如果真的需要拿到返回值,也是有方法的,方法比较常见的就是传参,传入一个引用类型,如下;
def worker(result):
    print("I'm Workering")
    result.append(1000)

result=[]
thread1 = threading.Thread(target=worker, name="worker-1",args=(result,))
thread1.start()
print(result)
# I'm Workering
# [1000]
    或通过修改全局变量方法的方式,但是这种不能使用,仅用于测试,因为会出现资源争用、紊乱问题,因为如果一千多个线程同时去修改这个全局变量,就很容易出现数据紊乱的问题,导致数据不一致;
result=None
def worker():
    print("I'm Workering")
    global result
    result=1000

thread1 = threading.Thread(target=worker, name="worker-1")
thread1.start()
print(result)
# I'm Workering
# 1000

并行处理

    并行处理主要是为了说明一个齐头并进的问题,我们需要知道,不管任何进程,不管进程是否创建其他子线程,这个进程里面都会有一个主线程,主线程和其他子线程是齐头并进的,是并行执行的;
    如下示例,修改了run()方法,将工作函数的返回值设置了成为了一个实例属性,但是最后在主线程中打印这个实例属性的时候却抛出AttributeError,这主要是因为,主线程和子线程是齐头并进的;
    主线程和子线程是并行的,如下代码抛出AttributeError的主要原因是子线程还没执行到self._ret=self._target(*self._args, **self._kwargs)这一行但是主线程已经执行到了print(thread1._ret)这一行,这实例属性还没有在这个实例中创建,但是在主进程却引用了这个实例属性,所以抛出了AttributeError错误,这就是典型的线程同步问题;
    这就完全表明了主进程和子进程是并行的一个基本案例;
class MyThread(threading.Thread):
    def run(self):  # 代码块内的源码,来自于源码
        try:
            if self._target:
                self._ret=self._target(*self._args, **self._kwargs)  # 修改
        finally:
            del self._target, self._args, self._kwargs
def worker():
    print("I'm workering...")
    return 100

thread1=MyThread(target=worker)
thread1.start()
print(thread1._ret)
# I'm workering...
# Traceback (most recent call last):
#   File "/usr/local/Project/cce/Py/edu.py", line 22, in <module>
#     print(thread1._ret)
# AttributeError: 'MyThread' object has no attribute '_ret'

多线程

    多线程其实就是不止一个线程,一个进程除了主线程之外还有多个线程那么就是多线程,它就是解决高并发的一种非常常见的解决方案,使用多线程可以把占据长时间的程序中的任务放到后台去处理;
    上面说了,真正干活的实际上就是线程。进程与线程的关系就像是工厂和工人的关系。那么现在工厂还是一个,但是干活的工人多了。那么效率自然就提高了,因为只有一个进程,所以多线程在提高效率的同时,并没有向系统伸手要更多的内存资源。因此使用起来性价比还是很高的。但是多线程虽然不更多的消耗内存,但是每个线程却需要CPU的的参与,相当于工厂虽然厂房就一间,可以有很多的工人干活。但是这些工人怎么干活还得靠厂长来指挥。工人太多了,厂长忙不过来安排一样效率不高。所以工人(线程)的数量最好还是在厂长(cpu)的能力(内核数)范围之内比较好;
    在Python中多线程的实现方式有两种,我的总结就是一种是函数形式的。一种是通过自己创建一个类并继承threading.Thread类来实现的。其实关于多线程用到模块,也是有两种,一种是thread,这个模块是最原始的多线程模块,但是这个模块据说是比较low的。于是就出来了个threading模块,它封装了thread模块,在Python2.7之后基本都是使用threading模块来实现多线程;
def worker():
    for _ in range(3):
        time.sleep(0.5)
        print(threading.current_thread().name, "Workering")

thread1 = threading.Thread(target=worker, name="Thread-1")
thread2 = threading.Thread(target=worker, name="Thread-2")
thread1.start()
thread2.start()

# Thread-1 Workering
# Thread-2 Workering
# Thread-2 Workering
# Thread-1 Workering
# Thread-2 Workering
# Thread-1 Workering
    可以看到如上示例的执行结果,可以看出来Thread-1和Thread-2是交替执行的,但需要知道的是它们是没有规律的,这完全取决于CPU的调度,这就是线程并行执行的一种交替执行的效果,在多线程的常见下,子线程可以创建N多个,但是一般来讲,不易过多,根据服务器的硬件资源相关;
    需要注意的一点,就是start()方法和run()方法的区别,start()方法主要是为了调用操作系统的系统调用来创建线程,并且将run()方法里面的工作函数跑起来,而run()方法只是将工作函数跑起来,不通过start()方法单独开启run()方法,这种形式是不会开启子线程来执行工作函数的,此时的工作函数会直接在主线程里面执行;
def worker():
    for _ in range(3):
        time.sleep(0.5)
        print(threading.current_thread().name, "Workering")

thread1 = threading.Thread(target=worker, name="Thread-1")
thread2 = threading.Thread(target=worker, name="Thread-2")
thread1.run()
thread2.run()

# MainThread Workering
# MainThread Workering
# MainThread Workering
# MainThread Workering
# MainThread Workering
# MainThread Workering

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注