我这样回答多线程并发,面试官非要跟我做朋友!

简介: 我这样回答多线程并发,面试官非要跟我做朋友!

1.单线程执行


Python的内置模块提供了两个线程模块:threading 和thread。

thread:是原生的

threading是扩展的

用法:

变量 = threading.Thread(target = 执行函数)

变量.strart()

举个例子:


import threading
#自定义test函数
def test():
    print("test threading")
#创建一个单线程,来执行test()函数
t = threading.Thread(target= test)
t.start()



结果:


2.多线程执行


单线程已经会使用,那么多线程还会远吗?

多线程只需要通过循环创建多个线程,并通过循环启动执行就可以了。

我们来看个例子,为了更直观,我把打印的内容修改成打印时间:

import threading
from datetime import *
#自定义test函数
def test():
    now = datetime.now()  #获取当前时间
    print("打印多线程执行时间:",now)
#自定义thr()函数,来执行多线程
def thr():
    threads = []  #自定义一个空的数组,用来存放线程组
    for i in range(10):  #设置循环10次
        t = threading.Thread(target=test)
        threads.append(t)  #把创建的的线程t,装到threads 数组中
    #启动线程
    for t in threads: 
        t.start()
#执行thr()函数进行多并发
if __name__ == "__main__"   :
    thr()


打印结果:

我们可以看到,我循环执行了10次,这时间相差的太小了,可以忽略不计。

如果,我设置1000次,10000次的话,如果还是这样写,是不是需要等待很长时间?服务器的压力会不会增加?资源消耗会不会增加?

那如何优化呢?

来看这个例子:

我们执行1000次并发,把这1000次拆成50个线程,每个线程循环20次,这样是不是就会快很多?

我们来看看执行效率

20200608153641340.png



修改后代码:

import threading
from datetime import *
#自定义test函数
def test():
    now = datetime.now()  #获取当前时间
    print("打印多线程执行时间:",now)
#设置50个线程
def looptest():
    for i in range(50):
        test()
#自定义thr()函数,来执行多线程
def thr():
    threads = []  #自定义一个空的数组,用来存放线程组
    for i in range(20):  #设置循环10次
        t = threading.Thread(target=test)
        threads.append(t)  #把创建的的线程t,装到threads 数组中
    #启动线程
    for t in threads:
        t.start()
#执行thr()函数进行多并发
if __name__ == "__main__"   :
    thr()


执行的结果:


20200608153814434.png


是不是快很多了。


3.守护线程


在了解守护线程之前,先来了解下主线程与子线程的区别。


主线程与子线程的区别:

每个线程都有一个唯一标示符,来区分线程中的主次关系的说法。

线程唯一标示符:Thread.CurrentThread.ManagedThreadID;

・UI界面和Main函数均为主线程。

・被Thread包含的“方法体”或者“委托”均为子线程。

・委托可以包含多个方法体,利用this.Invoke去执行。

・也可以定义多种方法体,放在Thread里面去执行。则此方法・体均为子线程。注意如果要修改UI界面的显示。则需要使用this.Invoke,否则会报异常。

・Main函数为主线程,id标示符与UI界面主线程相等。


对照上面的代码,main()就是主线程,thr()就是子线程。

>>即先启动main(),然后执行thr()启动子线程。


那么,什么是守护线程呢?

>>即当主线程执行完毕后,所有的子线程也被关闭(无论子线程是否执行完成)。默认是不设置守护线程的。


但是我们又为什么要用守护线程呢?

>>说的直接,就是为了防止死循环。

>>因为一个死循环如果不手动停止,我们都知道会一直的循环下去,直到资源耗尽。

那么守护线程的用法是什么呢?

>>setDaemon():默认是 False, 需要改成True才能启用。

举个例子:

import threading
from datetime import *
#自定义test函数
def test():
    x=0
    while (x ==0):    #修改成死循环
        print(datetime.now())
#自定义thr()函数,来执行多线程
def thr():
    threads = []  #自定义一个空的数组,用来存放线程组
    for i in range(20):  #设置循环10次
        t = threading.Thread(target=test)
        threads.append(t)  #把创建的的线程t,装到threads 数组中
        t.setDaemon(True) # 设置守护线程
    #启动线程
    for t in threads:
        t.start()
#执行thr()函数进行多并发
if __name__ == "__main__"   :
    thr()
    print("守护线程功能启用,end")

20200608160659208.png


注意一点:

setDaemon()必须放在 start()前面才有效果,否则无效果。

守护线程的意义是什么呢?

守护线程的意义在于处理主线程和子线程的关闭工作。


4.阻塞线程


强制程序停止,除了运用守护线程,还可以用到 阻塞线程,

如果说前者是强硬派,那么后者就属于温柔派。

那么我们再来看看阻塞线程;

阻塞线程:通过子线程 join()方法来阻塞线程,

让主线程等待子线程完成之后再往下执行,

等主线程执行完毕后在挂你吧所有子线程。

举个例子:

import threading
from datetime import *
def test():
    x =0
    while(x ==0):
        print(datetime.now())
def thr():
    threads = []
    for i in range(10):
        t = threading.Thread(target=test)
        threads.append(t)
        t.setDaemon(True)
    for t in  threads:
        t.start()
for t in threads:
    t.join()
if __name__ == "__main__":
    thr()
    print("阻塞线程功能启动,end")


这段代码,是不是让你有一种想喝(灭)水(火)的冲动??

那就对了,因为不能停止吗~ ~ ~ ~

那,这和什么都不设置不是一样???

莫着急,其实还是有一点区别的:

>>什么都不设置的情况下主线程是执行完的,仅等待子线程执行完,所以会打印end信息

>>而两个都设置的情况下,主线程会因为等待子线程结束而不往下执行,主线程无法执行完成,所以也就是无法关闭子线程,不会打印end信息


对于死循环这种情况,可以在join()设置timeout来控制

即,我们来设置个2秒钟,

    for t in threads:
        t.join(2)


但是你执行之后,会发现,为啥不是2秒停止,而是20秒才停止,是因为我们执行了10个线程, 而每个线程执行2秒,故10个线程timeout时间就是20秒。

是不是不太讲究,哎~~没办法,就顺了吧!!

那么阻塞线程的意义是啥呢?

>>阻塞线程的意义在于控制子线程与主线程的执行顺序!


5.并发测试框架


最后,来up一个并发测试框架,

算是今天学习的一个完美的收尾。

没有什么好说的,直接上代码。

# -*- coding: utf-8 -*-
"""
@ auth : carl_DJ
@ time : 2020-6-8
"""
import threading
from datetime import *
#自定义全局变量需要的线程数,20
thread_num = 20
#自定义全局变量每个线程需要循环的数量,10
one_work_num = 10
#自定义函数test()
def test():
    #编写测试代码
    now = datetime.now()
    print("打印时间戳:",now)
    #设置死循环
    #x =0
    #while (x ==1):
    #    print("打印时间戳:",now)
def working():
    # 引用全局变量
    global one_work_num
    #嵌套执行指定循环次数的 test()函数
    for i in range(0,one_work_num):
        test()
#自定义thr()函数,来执行多线程
def thr():
    #自定义一个空的数组,用来存放线程组
    threads = []
    #设置循环次数为thread_num
    for i in  range(thread_num):
        #将working()函数放入到线程中
        t =threading.Thread(target=working,name="T" + str(i))
        #设置守护线程
        t.setDaemon(True)
        threads.append(t)
    #启动线程并执行
    for t in threads:
        t.start()
    #设置阻塞线程
    for t in  threads:
        t.join(2)
if __name__ == "__main__":
    thr()







目录
相关文章
|
15天前
|
监控 Kubernetes Java
阿里面试:5000qps访问一个500ms的接口,如何设计线程池的核心线程数、最大线程数? 需要多少台机器?
本文由40岁老架构师尼恩撰写,针对一线互联网企业的高频面试题“如何确定系统的最佳线程数”进行系统化梳理。文章详细介绍了线程池设计的三个核心步骤:理论预估、压测验证和监控调整,并结合实际案例(5000qps、500ms响应时间、4核8G机器)给出具体参数设置建议。此外,还提供了《尼恩Java面试宝典PDF》等资源,帮助读者提升技术能力,顺利通过大厂面试。关注【技术自由圈】公众号,回复“领电子书”获取更多学习资料。
|
10天前
|
算法 安全 Java
Java线程调度揭秘:从算法到策略,让你面试稳赢!
在社招面试中,关于线程调度和同步的相关问题常常让人感到棘手。今天,我们将深入解析Java中的线程调度算法、调度策略,探讨线程调度器、时间分片的工作原理,并带你了解常见的线程同步方法。让我们一起破解这些面试难题,提升你的Java并发编程技能!
47 16
|
19天前
|
安全 Java 程序员
面试直击:并发编程三要素+线程安全全攻略!
并发编程三要素为原子性、可见性和有序性,确保多线程操作的一致性和安全性。Java 中通过 `synchronized`、`Lock`、`volatile`、原子类和线程安全集合等机制保障线程安全。掌握这些概念和工具,能有效解决并发问题,编写高效稳定的多线程程序。
55 11
|
18天前
|
Java Linux 调度
硬核揭秘:线程与进程的底层原理,面试高分必备!
嘿,大家好!我是小米,29岁的技术爱好者。今天来聊聊线程和进程的区别。进程是操作系统中运行的程序实例,有独立内存空间;线程是进程内的最小执行单元,共享内存。创建进程开销大但更安全,线程轻量高效但易引发数据竞争。面试时可强调:进程是资源分配单位,线程是CPU调度单位。根据不同场景选择合适的并发模型,如高并发用线程池。希望这篇文章能帮你更好地理解并回答面试中的相关问题,祝你早日拿下心仪的offer!
32 6
|
23天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
2月前
|
并行计算 算法 安全
面试必问的多线程优化技巧与实战
多线程编程是现代软件开发中不可或缺的一部分,特别是在处理高并发场景和优化程序性能时。作为Java开发者,掌握多线程优化技巧不仅能够提升程序的执行效率,还能在面试中脱颖而出。本文将从多线程基础、线程与进程的区别、多线程的优势出发,深入探讨如何避免死锁与竞态条件、线程间的通信机制、线程池的使用优势、线程优化算法与数据结构的选择,以及硬件加速技术。通过多个Java示例,我们将揭示这些技术的底层原理与实现方法。
97 3
|
3月前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
3月前
|
存储 缓存 算法
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
本文介绍了多线程环境下的几个关键概念,包括时间片、超线程、上下文切换及其影响因素,以及线程调度的两种方式——抢占式调度和协同式调度。文章还讨论了减少上下文切换次数以提高多线程程序效率的方法,如无锁并发编程、使用CAS算法等,并提出了合理的线程数量配置策略,以平衡CPU利用率和线程切换开销。
面试官:单核 CPU 支持 Java 多线程吗?为什么?被问懵了!
|
2月前
|
缓存 安全 Java
【JavaEE】——单例模式引起的多线程安全问题:“饿汉/懒汉”模式,及解决思路和方法(面试高频)
单例模式下,“饿汉模式”,“懒汉模式”,单例模式下引起的线程安全问题,解锁思路和解决方法
|
2月前
|
Java 调度