0

0

线程:如何以性能为目标来定义和限制执行?

DDD

DDD

发布时间:2024-11-13 16:33:13

|

1015人浏览过

|

来源于dev.to

转载

线程:如何以性能为目标来定义和限制执行?

线程,一种有助于开发现代高性能解决方案并成为不可或缺的工具。无论使用哪种语言,并行执行任务的能力都具有很大的吸引力。但显然有本叔叔的名言:“能力越大,责任越大。”如何以最佳方式使用该解决方案,以实现性能、更好地利用资源和应用程序健康?首先,有必要了解本主题的基本概念。

什么是“线程”?

线程是操作系统中进程执行的基本单位。它们允许程序在同一进程中同时执行多个操作。每个线程与主进程共享相同的内存空间,但可以独立执行,这对于可以并行执行的任务非常有用,例如输入/输出(i/o)操作、复杂计算或数据用户界面。 .

在许多系统上,线程由操作系统管理,操作系统为每个线程分配 cpu 时间并管理它们之间的上下文切换。在java、pythonc++等编程语言中,都有方便创建和管理线程的库和框架。

为什么要使用线程?

线程主要用于提高程序的效率和响应能力。使用线程,尤其是后端的原因是:

  • 并行:线程允许您同时执行多个操作,从而更好地利用可用的 cpu 资源,特别是在具有多个内核的系统上。

  • 性能:在 i/o 操作中,例如读写文件或网络通信,线程可以通过允许程序在等待这些任务完成的同时继续执行其他任务来帮助提高性能操作。

  • 模块化:线程可用于将程序划分为更小、更易于管理的部分,每个部分执行特定的任务。

但是,仔细管理线程非常重要,因为不正确的使用可能会导致竞争条件、死锁和调试困难等问题。为了更好地管理它们,使用了线程池解决方案。

什么是线程池以及为什么需要它?

线程池是一种软件设计模式,涉及创建和管理可重复使用来执行任务的线程池。线程池不会为每个任务重复创建和销毁线程,而是维护固定数量的线程,准备根据需要执行任务。这可以显着提高需要处理许多并发任务的应用程序的性能。使用线程池的优点是:

  • 提高性能:创建和销毁线程在资源方面是一项昂贵的操作。线程池通过重用现有线程来最小化此成本。

    MvMmall 网店系统
    MvMmall 网店系统

    免费的开源程序长期以来,为中国的网上交易提供免费开源的网上商店系统一直是我们的初衷和努力奋斗的目标,希望大家一起把MvMmall网上商店系统的免费开源进行到底。2高效的执行效率由资深的开发团队设计,从系统架构,数据库优化,配以通过W3C验证的面页模板,全面提升页面显示速度和提高程序负载能力。3灵活的模板系统MvMmall网店系统程序代码与网页界面分离,灵活的模板方案,完全自定义模板,官方提供免费模

    下载
  • 资源管理:控制运行的线程数量,避免过多的线程创建导致系统过载。

  • 易于使用:简化线程管理,使开发人员能够专注于应用程序逻辑而不是线程管理。

  • 可扩展性:帮助扩展应用程序以有效地处理大量并发任务。

如何设置池中的线程限制

好吧,我当然必须创建一个线程池才能更好地利用此功能,但很快出现的一个问题是:“池应该包含多少个线程?”。按照基本逻辑,越多越好,对吗?如果所有事情都可以并行完成,那么很快就会完成,因为速度会更快。因此,最好不要限制线程数量,或者设置一个较高的数字,这样就不会出现问题。正确吗?
这是一个公平的说法,所以让我们测试一下。此测试的代码是用 kotlin 编写的,只是为了熟悉并易于编写示例。这一点与语言无关。
通过 4 个示例来探索不同的系统性质。示例 1 和示例 2 是为了使用 cpu 进行大量数学运算,即进行大量处理。示例3重点关注i/o,示例是读取文件,最后,示例4是并行api调用的情况,同样关注i/o。它们都使用不同大小的池,分别具有 1、2、4、8、16、32、50、100 和 500 个线程。所有过程发生超过 500 次。

示例 1 - 计算 1 到 100000 之间有多少个质数的代码

import kotlinx.coroutines.*
import kotlin.math.sqrt
import kotlin.system.measuretimemillis

fun isprime(number: int): boolean {
    if (number <= 1) return false
    for (i in 2..sqrt(number.todouble()).toint()) {
        if (number % i == 0) return false
    }
    return true
}

fun countprimesinrange(start: int, end: int): int {
    var count = 0
    for (i in start..end) {
        if (isprime(i)) {
            count++
        }
    }
    return count
}

@optin(delicatecoroutinesapi::class)
fun main() = runblocking {
    val rangestart = 1
    val rangeend = 100_000
    val numberofthreadslist = listof(1, 2, 4, 8, 16, 32, 50, 100, 500)

    for (numberofthreads in numberofthreadslist) {
        val customdispatcher = newfixedthreadpoolcontext(numberofthreads, "custompool")
        val chunksize = (rangeend - rangestart + 1) / numberofthreads
        val timetaken = measuretimemillis {
            val jobs = mutablelistof>()
            for (i in 0 until numberofthreads) {
                val start = rangestart + i * chunksize
                val end = if (i == numberofthreads - 1) rangeend else start + chunksize - 1
                jobs.add(async(customdispatcher) { countprimesinrange(start, end) })
            }
            val totalprimes = jobs.awaitall().sum()
            println("total de números primos encontrados com $numberofthreads threads: $totalprimes")
        }
        println("tempo levado com $numberofthreads threads: $timetaken ms")
        customdispatcher.close()
    }
}
示例1 控制台输出
total de números primos encontrados com 1 threads: 9592
tempo levado com 1 threads: 42 ms
total de números primos encontrados com 2 threads: 9592
tempo levado com 2 threads: 17 ms
total de números primos encontrados com 4 threads: 9592
tempo levado com 4 threads: 8 ms
total de números primos encontrados com 8 threads: 9592
tempo levado com 8 threads: 8 ms
total de números primos encontrados com 16 threads: 9592
tempo levado com 16 threads: 16 ms
total de números primos encontrados com 32 threads: 9592
tempo levado com 32 threads: 12 ms
total de números primos encontrados com 50 threads: 9592
tempo levado com 50 threads: 19 ms
total de números primos encontrados com 100 threads: 9592
tempo levado com 100 threads: 36 ms
total de números primos encontrados com 500 threads: 9592
tempo levado com 500 threads: 148 ms

示例 2 - 计算斐波那契数列上第 30 个数字的代码

import kotlinx.coroutines.delicatecoroutinesapi
import kotlinx.coroutines.launch
import kotlinx.coroutines.newfixedthreadpoolcontext
import kotlinx.coroutines.runblocking
import kotlin.system.measuretimemillis

fun fibonacci(n: int): long {
    return if (n <= 1) n.tolong() else fibonacci(n - 1) + fibonacci(n - 2)
}

@optin(delicatecoroutinesapi::class)
fun main() = runblocking {
    val numberofthreadslist = listof(1, 2, 4, 8, 16, 32, 50, 100, 500)

    for (numberofthreads in numberofthreadslist) {
        val customdispatcher = newfixedthreadpoolcontext(numberofthreads, "custompool")
        val numberstocalculate = mutablelistof()
        for (i in 1..1000) {
            numberstocalculate.add(30)
        }
        val timetaken = measuretimemillis {
            val jobs = numberstocalculate.map { number ->
                launch(customdispatcher) {
                    fibonacci(number)
                }
            }
            jobs.foreach { it.join() }
        }
        println("tempo levado com $numberofthreads threads: $timetaken ms")
        customdispatcher.close()
    }
}
示例2 控制台输出
tempo levado com 1 threads: 4884 ms
tempo levado com 2 threads: 2910 ms
tempo levado com 4 threads: 1660 ms
tempo levado com 8 threads: 1204 ms
tempo levado com 16 threads: 1279 ms
tempo levado com 32 threads: 1260 ms
tempo levado com 50 threads: 1364 ms
tempo levado com 100 threads: 1400 ms
tempo levado com 500 threads: 1475 ms

示例 3 - 读取随机数文件并在读取结束时添加它们的代码

import kotlinx.coroutines.*
import java.io.file
import kotlin.system.measuretimemillis

@optin(delicatecoroutinesapi::class)
fun main() = runblocking {
    val file = file("numeros_aleatorios.txt")

    if (!file.exists()) {
        println("arquivo não encontrado!")
        return@runblocking
    }
    val numberofthreadslist = listof(1, 2, 4, 8, 16, 32, 50, 100, 500)
    for (numberofthreads in numberofthreadslist) {
        val customdispatcher = newfixedthreadpoolcontext(numberofthreads, "custompool")
        val timetaken = measuretimemillis {
            val jobs = mutablelistof>()
            file.uselines { lines ->
                lines.foreach { line ->
                    jobs.add(async(customdispatcher) {
                        processline(line)
                    })
                }
            }
            val totalsum = jobs.awaitall().sum()
            println("total da soma com $numberofthreads threads: $totalsum")
        }
        println("tempo levado com $numberofthreads threads: $timetaken ms")
        customdispatcher.close()
    }

}

fun processline(line: string): int {
    return line.toint() + 10
}
示例3 控制台输出
total da soma de 1201 linhas com 1 threads: 60192
tempo levado com 1 threads: 97 ms
total da soma de 1201 linhas com 2 threads: 60192
tempo levado com 2 threads: 28 ms
total da soma de 1201 linhas com 4 threads: 60192
tempo levado com 4 threads: 30 ms
total da soma de 1201 linhas com 8 threads: 60192
tempo levado com 8 threads: 26 ms
total da soma de 1201 linhas com 16 threads: 60192
tempo levado com 16 threads: 33 ms
total da soma de 1201 linhas com 32 threads: 60192
tempo levado com 32 threads: 35 ms
total da soma de 1201 linhas com 50 threads: 60192
tempo levado com 50 threads: 44 ms
total da soma de 1201 linhas com 100 threads: 60192
tempo levado com 100 threads: 66 ms
total da soma de 1201 linhas com 500 threads: 60192
tempo levado com 500 threads: 297 ms

示例 4 - 调用 api 500 次的代码

import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.request.*
import kotlinx.coroutines.delicatecoroutinesapi
import kotlinx.coroutines.launch
import kotlinx.coroutines.newfixedthreadpoolcontext
import kotlinx.coroutines.runblocking
import kotlin.system.measuretimemillis

@optin(delicatecoroutinesapi::class)
fun main() = runblocking {
    val client = httpclient(cio)

    try {
        val numberofthreadslist = listof(1, 2, 4, 8, 16, 32, 50, 100, 500)
        for (numberofthreads in numberofthreadslist) {
            val customdispatcher = newfixedthreadpoolcontext(numberofthreads, "custompool")
            val timetaken = measuretimemillis {
                repeat(500) {
                    val jobs = launch(customdispatcher) { client.get("http://127.0.0.1:5000/example") }
                    jobs.join()
                }
            }
            println("tempo levado com $numberofthreads threads: $timetaken ms")
            customdispatcher.close()
        }
    } catch (e: exception) {
        println("erro ao conectar à api: ${e.message}")
    } finally {
        client.close()
    }
}
示例4 控制台输出
Tempo levado com 1 threads: 7104 ms
Tempo levado com 2 threads: 4793 ms
Tempo levado com 4 threads: 4170 ms
Tempo levado com 8 threads: 4310 ms
Tempo levado com 16 threads: 4028 ms
Tempo levado com 32 threads: 4089 ms
Tempo levado com 50 threads: 4066 ms
Tempo levado com 100 threads: 3978 ms
Tempo levado com 500 threads: 3777 ms

示例 1 到示例 3 有一个共同的行为,它们在达到 8 个线程时性能都变得更高,然后处理时间再次增加,但示例 4 却没有,这说明了什么?总是使用尽可能多的线程不是很有趣吗?

简单而快速的答案是

我的机器的处理器有 8 个核心,也就是说,它可以同时执行 8 个任务,超过这个时间会随着管理每个线程状态的时间而增加,最终导致性能下降。

好的,这回答了示例 1 到 3,但是示例 4 呢?为什么启动的线程越多性能就会提高?

简单,因为它是一个集成,机器没有处理,它基本上等待响应,它保持“睡眠”直到响应到达,所以是的,这里线程的数量可以更大。但要小心,并不意味着可以有尽可能多的线程,线程会导致资源耗尽,不加区别地使用它们会产生相反的效果,会影响服务的整体健康状况。
因此,要定义池将拥有的线程数,最简单、最安全的方法是分离将要执行的任务的性质。它们分为两部分:

  • 不需要处理的任务:
    当任务类型不需要处理时,可以创建比机器上的处理器核心更多的线程。发生这种情况是因为不需要处理信息来完成线程,基本上这种性质的线程在大多数情况下都期望来自集成的响应,例如写入数据库或来自 api 的响应。

  • 需要处理的任务:
    当解决方案有处理时,即机器实际在做工作时,最大线程数必须是机器处理器的核心数。这是因为处理器核心无法同时执行多于一件事。例如,如果运行该解决方案的处理器有 4 个核心,那么它的线程池必须是您的处理器核心的大小,即 4 线程池。

结论

在考虑线程池时,首先要定义的一点不一定是限制其大小的数量,而是所执行任务的性质。线程对服务的性能有很大帮助,但必须以最佳方式使用它们,以免产生相反的效果并降低性能,甚至更糟糕的是,导致整个服务的健康状况受到影响。很明显,较小的池最终更倾向于处理大量使用的任务,换句话说,即 cpu 受限的任务。如果您不确定使用线程的解决方案是否具有大量使用处理的行为,请谨慎起见,将您的池限制为机器上的处理器数量,相信我,这会节省让你很头疼。

相关文章

数码产品性能查询
数码产品性能查询

该软件包括了市面上所有手机CPU,手机跑分情况,电脑CPU,电脑产品信息等等,方便需要大家查阅数码产品最新情况,了解产品特性,能够进行对比选择最具性价比的商品。

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

525

2023.08.10

数据库三范式
数据库三范式

数据库三范式是一种设计规范,用于规范化关系型数据库中的数据结构,它通过消除冗余数据、提高数据库性能和数据一致性,提供了一种有效的数据库设计方法。本专题提供数据库三范式相关的文章、下载和课程。

360

2023.06.29

如何删除数据库
如何删除数据库

删除数据库是指在MySQL中完全移除一个数据库及其所包含的所有数据和结构,作用包括:1、释放存储空间;2、确保数据的安全性;3、提高数据库的整体性能,加速查询和操作的执行速度。尽管删除数据库具有一些好处,但在执行任何删除操作之前,务必谨慎操作,并备份重要的数据。删除数据库将永久性地删除所有相关数据和结构,无法回滚。

2083

2023.08.14

vb怎么连接数据库
vb怎么连接数据库

在VB中,连接数据库通常使用ADO(ActiveX 数据对象)或 DAO(Data Access Objects)这两个技术来实现:1、引入ADO库;2、创建ADO连接对象;3、配置连接字符串;4、打开连接;5、执行SQL语句;6、处理查询结果;7、关闭连接即可。

349

2023.08.31

MySQL恢复数据库
MySQL恢复数据库

MySQL恢复数据库的方法有使用物理备份恢复、使用逻辑备份恢复、使用二进制日志恢复和使用数据库复制进行恢复等。本专题为大家提供MySQL数据库相关的文章、下载、课程内容,供大家免费下载体验。

256

2023.09.05

vb中怎么连接access数据库
vb中怎么连接access数据库

vb中连接access数据库的步骤包括引用必要的命名空间、创建连接字符串、创建连接对象、打开连接、执行SQL语句和关闭连接。本专题为大家提供连接access数据库相关的文章、下载、课程内容,供大家免费下载体验。

326

2023.10.09

数据库对象名无效怎么解决
数据库对象名无效怎么解决

数据库对象名无效解决办法:1、检查使用的对象名是否正确,确保没有拼写错误;2、检查数据库中是否已存在具有相同名称的对象,如果是,请更改对象名为一个不同的名称,然后重新创建;3、确保在连接数据库时使用了正确的用户名、密码和数据库名称;4、尝试重启数据库服务,然后再次尝试创建或使用对象;5、尝试更新驱动程序,然后再次尝试创建或使用对象。

413

2023.10.16

vb连接access数据库的方法
vb连接access数据库的方法

vb连接access数据库方法:1、使用ADO连接,首先导入System.Data.OleDb模块,然后定义一个连接字符串,接着创建一个OleDbConnection对象并使用Open() 方法打开连接;2、使用DAO连接,首先导入 Microsoft.Jet.OLEDB模块,然后定义一个连接字符串,接着创建一个JetConnection对象并使用Open()方法打开连接即可。

411

2023.10.16

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

54

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.4万人学习

Django 教程
Django 教程

共28课时 | 3.7万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号