https://blog.waterflow.link/articles/1662974432717
一个进程包含可以由任何进程分配的公共资源。这些资源包括但不限于内存地址空间、文件句柄、设备和线程。
一个进程会包含下面一些属性:
线程是轻量级的进程,一个线程将在进程内的所有线程之间共享进程的资源,如代码、数据、全局变量、文件和内存地址空间。但是栈和寄存器不会共享,每个线程都有自己的栈和寄存器
线程的优点:
用户级线程也称为绿色线程,如:C 中的coroutine、Go 中的 goroutine 和 Ruby 中的 Fiber

该进程维护一个内存地址空间,处理文件,以及正在运行的应用程序的设备和线程。操作系统调度程序决定哪些线程将在任何给定的 CPU 上接收时间
因此,与耗时和资源密集型的进程创建相比,在一个进程中创建多个用户线程(goroutine)效率更高。
在Go中用户级线程被称作Goroutine,在创建goroutine时需要做到:
其中阻塞调用可能是下面一些原因:
为什么go需要调度goroutine?
Go 使用称为 goroutine 的用户级线程,它比内核级线程更轻且更便宜。 例如,创建一个初始 goroutine 将占用 2KB 的堆栈大小,而内核级线程将占用 8KB 的堆栈大小。 还有,goroutine 比内核线程有更快的创建、销毁和上下文切换,所以 go 调度器 需要退出来调度 goroutine。OS 不能调度用户级线程,OS 只知道内核级线程。 Go 调度器 将 goroutine 多路复用到内核级线程,这些线程将在不同的 CPU 内核上运行
什么时候会调度goroutine?
如果有任何操作应该或将会影响 goroutine 的执行,比如 goroutine 的启动、等待执行和阻塞调用等……
go调度 如何将 goroutine 多路复用到内核线程中?
1、1:1调度(1个线程对应一个goroutine)
2、N:1调度(在单个内核线程上多路复用所有 goroutine)
我们看下下面的例子,只为go分配了1个processer去处理2个goroutine:
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
// 分配 1 个逻辑处理器供调度程序使用
runtime.GOMAXPROCS(1)
var wg sync.WaitGroup
wg.Add(2)
fmt.Println("Starting Goroutines")
// 开一个go协程打印字母
go func() {
defer wg.Done()
time.Sleep(time.Second)
// 打印3次字母
for count := 0; count < 3; count++ {
for ch := 'a'; ch < 'a'+26; ch++ {
fmt.Printf("%c ", ch)
}
fmt.Println()
}
}()
// 开一个go协程打印数字
go func() {
defer wg.Done()
// 打印3次数字
for count := 0; count < 3; count++ {
for n := 1; n <= 26; n++ {
fmt.Printf("%d ", n)
}
fmt.Println()
}
}()
// 等待返回
fmt.Println("Waiting To Finish")
wg.Wait()
fmt.Println("\nTerminating Program")
}
看下结果:
go run main.go
Starting Goroutines
Waiting To Finish
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
a b c d e f g h i j k l m n o p q r s t u v w x y z
a b c d e f g h i j k l m n o p q r s t u v w x y z
a b c d e f g h i j k l m n o p q r s t u v w x y z
Terminating Program
可以看到这俩个goroutine是串行执行的,要么先完成第一个goroutine,要么先完成第二个goroutine,并不是并发执行的。
那如何去实现并发执行呢?
我们同样设置runtime.GOMAXPROCS为1,但是在goroutine中我们在不同的时机加入阻塞goroutine的时间函数time.Sleep,我们看下会有什么不同的结果。
package main
import (
"fmt"
"runtime"
"sync"
"time"
)
func main() {
// 分配 1 个逻辑处理器供调度程序使用
runtime.GOMAXPROCS(1)
var wg sync.WaitGroup
wg.Add(2)
fmt.Println("Starting Goroutines")
// 开一个go协程打印字母
go func() {
defer wg.Done()
time.Sleep(time.Second)
// 打印3次字母
for count := 0; count < 3; count++ {
for ch := 'a'; ch < 'a'+26; ch++ {
if count == 0 {
time.Sleep(10 * time.Millisecond)
}
if count == 1 {
time.Sleep(30 * time.Millisecond)
}
if count == 2 {
time.Sleep(50 * time.Millisecond)
}
fmt.Printf("%c ", ch)
}
fmt.Println()
}
}()
// 开一个go协程打印数字
go func() {
defer wg.Done()
// 打印3次数字
for count := 0; count < 3; count++ {
for n := 1; n <= 26; n++ {
if count == 0 {
time.Sleep(20 * time.Millisecond)
}
if count == 1 {
time.Sleep(40 * time.Millisecond)
}
if count == 2 {
time.Sleep(60 * time.Millisecond)
}
fmt.Printf("%d ", n)
}
fmt.Println()
}
}()
// 等待返回
fmt.Println("Waiting To Finish")
wg.Wait()
fmt.Println("\nTerminating Program")
}
看下结果:
go run main.go
Starting Goroutines
Waiting To Finish
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
1 2 3 4 5 6 7 8 9 10 11 a 12 b c d e 13 f g h i 14 j k l m 15 n o p 16 q r s t 17 u v w x 18 y z
19 a b 20 c 21 d 22 e f 23 g 24 h 25 i j 26
k l 1 m n 2 o p 3 q r 4 s t 5 u v 6 w x 7 y z
8 a 9 b 10 c 11 d 12 e f 13 g 14 h 15 i 16 j 17 k l 18 m 19 n 20 o 21 p 22 q r 23 s 24 t 25 u 26
v w x y z
Terminating Program
通过上面的结果我们可以看到,当goroutine1阻塞时,go调度器会调度goroutine2执行。
我们可以得出:
3、线程池
4、M: N 线程共享运行队列调度(GMP)
我们上面提到过导致goroutine阻塞调用可能是下面一些原因:
下面看一些goroutine阻塞的例子:
package main
import (
"time"
"fmt"
"sync"
"os"
"net/http"
"io/ioutil"
)
// 全局变量
var worker int
func writeToFile(wg *sync.WaitGroup,){
defer wg.Done()
file, _ := os.OpenFile("file.txt", os.O_RDWR|os.O_CREATE, 0755) // 系统调用阻塞
resp, _ := http.Get("https://blog.waterflow.link/articles/1662706601117") // 网络IO阻塞
body, _ := ioutil.ReadAll(resp.Body) // 系统调用阻塞
file.WriteString(string(body))
}
func workerCount(wg *sync.WaitGroup, m *sync.Mutex, ch chan string) {
// Lock() 给共享资源上锁
// 独占访问状态,
// 增加worker的值,
// Unlock() 释放锁
m.Lock() // Mutex阻塞
worker = worker + 1
ch <- fmt.Sprintf("Worker %d is ready",worker)
m.Unlock()
// 返回, 通知WaitGroup完成
wg.Done()
}
func printWorker(wg *sync.WaitGroup, done chan bool, ch chan string){
for i:=0;i<100;i++{
fmt.Println(<-ch) // Channel阻塞
}
wg.Done()
done <-true
}
func main() {
ch :=make(chan string)
done :=make(chan bool)
var mu sync.Mutex
var wg sync.WaitGroup
for i:=1;i<=100;i++{
wg.Add(1)
go workerCount(&wg,&mu,ch)
}
wg.Add(2)
go writeToFile(&wg)
go printWorker(&wg,done,ch)
wg.Wait()
<-done // Channel阻塞
<-time.After(1*time.Second) // Timer阻塞
close(ch)
close(done)
}
下面我们看看go调度器在上面这些例子中是如何工作的:

互斥锁、定时器和网络 IO 使用相同的机制
如果一个 goroutine 在系统调用中被阻塞,那么情况就不同了,因为我们不知道内核空间发生了什么。 通道是在用户空间中创建的,因此我们可以完全控制它们,但在系统调用的情况下,我们没法控制它们。
阻塞系统调用不仅会阻塞 goroutine 还会阻塞内核线程。
假设一个 goroutine 被安排在一个内核线程上的系统调用,当一个内核线程完成执行时,它将唤醒另一个内核线程(线程重用),该线程将拾取另一个 goroutine 并开始执行它。 这是一个理想的场景,但在实际情况下,我们不知道系统调用将花费多少时间,因此我们不能依赖内核线程来唤醒另一个线程,我们需要一些代码级逻辑来决定何时 在系统调用的情况下唤醒另一个线程。 这个逻辑在 golang 中实现为 runtime·entersyscall()和 runtime·exitsyscall()。 这意味着内核线程的数量可以超过核心的数量。
当对内核进行系统调用时,它有两个关键点,一个是进入时机,另一个是退出时机。

操作系统可以支持多少内核线程?
在 Linux 内核中,此参数在文件 /proc/sys/kernel/threads-max 中定义,该文件用于特定内核。
sh:~$ cat /proc/sys/kernel/threads-max 94751
这里输出94751表示内核最多可以执行94751个线程
每个 Go 程序可以支持多少个 goroutine?
调度中没有内置对 goroutine 数量的限制。
每个 GO程序 可以支持多少个内核线程?
默认情况下,运行时将每个程序限制为最多 10,000 个线程。可以通过调用 runtime/debug 包中的 SetMaxThreads 函数来更改此值。
总结:
5、M:N 线程分布式运行队列调度器
为了解决每个线程同时尝试访问互斥锁的可扩展问题,维护每个线程的本地运行队列

从上面的动图可以看到:
结论:
如果线程数大于内核数,那么会有什么问题呢?
在分布式运行队列调度中,我们知道每个线程都有自己的本地运行队列,其中包含有关接下来将执行哪个 goroutine 的信息。 同样由于系统调用,线程数会增加,并且大多数时候它们的本地运行队列是空的。 因此,如果线程数大于核心数,则每个线程必须扫描所有线程本地运行队列,并且大部分时间它们是空的,所以如果线程过多,这个过程是耗时的并且解决方案 效率不高,因此我们需要将线程扫描限制为使用 M:P:N 线程模型求解的常数。
6、M:P: N 线程
如何检查逻辑处理器的数量?
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println(runtime.NumCPU())
}
分布式 M:P:N 调度例子

在系统调用期间执行P的切换

在work-stealing期间,只需要扫描固定数量的队列,因为逻辑处理器的数量是有限的。
如何选择下一个要运行的 goroutine ?
Go 调度器 将按以下顺序检查以选择下一个要执行的 goroutine
本地运行队列

全局运行队列

Network poller

Work Stealing

总结:
Go 调度的局限性
翻译自:
我需要一个用于大型动态任务集合的调度程序。目前我正在查看resque-scheduler,rufus-scheduler,和clockwork.如果您提供有关选择使用哪一个(或其他替代方案)的建议,我将不胜感激。一些细节:有大量要定期执行的任务(最多100K)。最短执行周期为1h。新任务可能会不时出现。现有任务可能会更改或删除。调度延迟最小化在这里不是关键任务(可扩展性和可持续性最重要)。任务执行不是繁重的操作,可以轻松并行。总结,我需要类似cron的Ruby项目,它可以处理大量动态变化的任务集合。更新:我花了一天时间尝试调度库,现在我想简单总结一下新获得的经验。我已经不再关注Cloc
我正在为我的工作用Ruby编写一个作业调度应用程序(主要是为了以给定的频率使用各种协议(protocol)移动文件)我的主循环是这样的:whiletruedo#somecodetolaunchtheproperjobsleepCONFIG["interval"]end它的工作就像一个魅力,但我不确定它是否足够安全,因为该应用程序可能在运行cpu密集型软件的服务器上运行。是否有另一种方法可以做同样的事情,或者sleep()对我来说是否足够安全? 最佳答案 每当我觉得需要阻塞时,我都会使用事件循环;通常是libev。这是一个Ruby绑定
我正在尝试使用rails3和resquescheduler测试future的调度作业:以下是我正在使用的代码,但我收到NoMethodErrorset_schedule。Resque.set_schedule("1",{:cron=>"306**1",:class=>"Notify",:queue=>"username",:message=>'notificationmessage'})我尝试使用简单的入队Resque.enqueue(Notify,params[:message])并且效果很好。更新:以下是我遇到的错误:undefinedmethod`set_schedule'for
我一直在努力理解double-dispatchpattern并且很难过。我终于尝试了一个示例程序来帮助自己理解。Here's要旨。但后来我决定尝试一下withoutDoubledispatch解决方案看起来并没有比平时更糟糕。我做错了什么?编辑:根据建议,我发布了这个问题here.保留此链接以进行重定向。 最佳答案 在单一分派(dispatch)中——您在大多数现代OO语言中看到的——方法是根据单个对象的运行时类型分派(dispatch)的。这显示为点运算符(在ruby、java、javascript等中)或箭头运算符(perl
数据同步的方式数据同步的2大方式基于SQL查询的CDC(ChangeDataCapture):离线调度查询作业,批处理。把一张表同步到其他系统,每次通过查询去获取表中最新的数据。也就是我们说的基于SQL查询抽取;无法保障数据一致性,查的过程中有可能数据已经发生了多次变更;不保障实时性,基于离线调度存在天然的延迟;工具软件以Kettle(ApacheHop最新版)、DataX为代表,需要结合任务调度系统使用。基于日志的CDC:实时消费日志,流处理,例如MySQL的binlog日志完整记录了数据库中的变更,可以把binlog文件当作流的数据源;保障数据一致性,因为binlog文件包含了所有历史变更
🍎道阻且长,行则将至。🍓目录一、遗传算法🌱1.遗传算法简介2.遗传操作2.1选择2.2交叉2.3变异3.遗传算法流程二、实现遗传算法🌴1.编码与初始化2.适应度计算和选择3.交叉3.突变进化过程调用EasyX库进行绘图三、作业调度🌴1.调度模型2.遗传算法应用3.实现四、遗传算法的数学分析🌲1.模式定理2.积木块假设3.收敛性分析一、遗传算法🌱根据遗传学的理论,生物的进化发展来源于三大动力:自然选择、遗传和突变。自然选择就是自然环境对不同表现型生物有不同的影响,使用适应度来度量这种影响,适应度较好的生物个体对环境亲和力较高,有较大的几率可以存活下来,而适应度较差的容易被淘汰。遗传是指亲子之间或
我正在使用剑道调度程序。调度器网格中添加了事件。在每个事件的鼠标悬停时,右上角都会出现一个小的(x)。即销毁该事件的事件,单击该事件会显示一条警告消息“您确定要删除此事件吗?”如果单击"is",它将继续并删除该事件。所以这是我的要求。如您所见,显示的一周内有3个预定事件。我想要那个,蓝色圈出的不应该有删除选项,但红色圈出的应该有。换句话说,我想限制kendoscheduler中的一些事件被删除。场景:假设任何具有描述的事件都无法删除。链接:http://demos.telerik.com/kendo-ui/scheduler/move-resize更新我可以做一个服务器端调用来检查描述
我正在使用Dojo框架通过交叉浏览DOM操作和事件管理来帮助我进行Javascript开发。最后,我希望在对象之间使用自定义事件调度。但我没有找到任何东西。我阅读了有关订阅/发布的内容,但这并不是我想要的。这是我想要做的:varmyObject=newCustomObject();dojo.connect(myObject,'onCustomEvent',function(argument){console.log('customeventfiredwithargument:'+argument);});varCustomObject=(function(){CustomObject=
文章目录前言一、先来先服务(FCFS)二、最短时间优先(SJF)三、最高响应比优先(HRRN)四、时间片轮转(RR)五、优先级调度六、多级反馈队列总结前言本文的主要内容是调度算法的介绍,包括先来先服务(FCFS)、最短时间优先(SJF)、最高响应比优先(HRRN)、时间片轮转(RR)、优先级调度和多级反馈队列这六种方法,这些调度算法会从其算法思想、算法规则、该方法用于作业调度还是进程调度、进程调度的方式(抢占式和非抢占式)、优缺点以及是否会导致饥饿这几个方面展开介绍,同时在介绍每种调度算法时还会举例子辅助理解。一、先来先服务(FCFS)饥饿是进程或者作业长期得不到服务而产生的一种状态。先来先服
我经常使用Flash,我的类(class)使用EventDispatcher类,它允许我定义类的自定义事件。我如何在JavaScript中执行此操作。我想做这样的事情:varMyClass=function(){};MyClass.prototype={test:function(){dispatchEvent('ON_TEST');}};varmc=newMyClass();mc.addEventListener('ON_TEST',handler);functionhandler(){alert('working...')}这如何通过JavaScript实现?