Go语言并发之美:解释其中内核、外延

2023-10-31

多核处理器越来越普及,那有没有一种简单的办法,能够让我们写的软件释放多核的威力?答案是:Yes。随着Golang, Erlang, Scale等为并发设计的程序语言的兴起,新的并发模式逐渐清晰。正如过程式编程和面向对象一样,一个好的编程模式需要有一个极其简洁的内核,还有在此之 上丰富的外延,可以解决现实世界中各种各样的问题。本文以GO语言为例,解释其中内核、外延。

并发模式之内核

这种并发模式的内核只需要协程和通道就够了。其中协程负责执行代码,通道负责在协程之间传递事件。

  

并发编程一直以来都是个非常困难的工作。要想编写一个良好的并发程序,我们不得不了解线程, 锁,semaphore,barrier甚至CPU更新高速缓存的方式,而且他们个个都有怪脾气,处处是陷阱。笔者除非万不得以,决不会自己操作这些底层 并发元素。一个简洁的并发模式不需要这些复杂的底层元素,只需协程和通道就够了。

协程是轻量级的线程。在过程式编程中,当调用一个过程的时候,需要等待其执行完才返回。而调用一个协程的时候,不需要等待其执行完,会立即返回。协程十分 轻量,Go语言可以在一个进程中执行有数以十万计的协程,依旧保持高性能。而对于普通的平台,一个进程有数千个线程,其CPU会忙于上下文切换,性能急剧 下降。随意创建线程可不是一个好主意,但是我们可以大量使用的协程。

通道是协程之间的数据传输通道。通道可以在众多的协程之间传递数据,具体可以值也可以是个引用。通道有两种使用方式。

·  协程可以试图向通道放入数据,如果通道满了,会挂起协程,直到通道可以为他放入数据为止。

·  协程可以试图向通道索取数据,如果通道没有数据,会挂起协程,直到通道返回数据为止。

 如此,通道就可以在传递数据的同时,控制协程的运行。有点像事件驱动,也有点像阻塞队列。这两个概念非常的简单,各个语言平台都会有相应的实现。在Java和C上也各有库可以实现两者。

  

只要有协程和通道,就可以优雅的解决并发的问题。不必使用其他和并发有关的概念。那如何用这两把利刃解决各式各样的实际问题呢?

并发模式之外延

协程相较于线程,可以大量创建。打开这扇门,我们拓展出新的用法,可以做生成器,可以让函数返回“服务”,可以让循环并发执行,还能共享变量。但是出现新 的用法的同时,也带来了新的棘手问题,协程也会泄漏,不恰当的使用会影响性能。下面会逐一介绍各种用法和问题。演示的代码用GO语言写成,因为其简洁明 了,而且支持全部功能。

生成器

有的时候,我们需要有一个函数能不断生成数据。比方说这个函数可以读文件,读网络,生成自增长序列,生成随机数。这些行为的特点就是,函数的已知一些变量,如文件路径。然后不断调用,返回新的数据。

下面生成随机数为例,以让我们做一个会并发执行的随机数生成器。

非并发的做法是这样的:

// 函数rand_generator_1 ,返回 int

funcrand_generator_1() int {

         return rand.Int()

}

上面是一个函数,返回一个int。假如rand.Int()这个函数调用需要很长时间等待,那该函数的调用者也会因此而挂起。所以我们可以创建一个协程,专门执行rand.Int()。

// 函数rand_generator_2,返回通道(Channel)

funcrand_generator_2() chan int {

         // 创建通道

         out := make(chan int)

         // 创建协程

         go func() {

                   for {

                            //向通道内写入数据,如果无人读取会等待

                            out <- rand.Int()

                   }

         }()

         return out

funcmain() {

         // 生成随机数作为一个服务

         rand_service_handler :=rand_generator_2()

         // 从服务中读取随机数并打印

         fmt.Printf("%d\n",<-rand_service_handler)

}

上面的这段函数就可以并发执行了rand.Int()。有一点值得注意到函数的返回可以理解为一个“服务”。但我们需要获取随机数据时候,可以随时向这个 服务取用,他已经为我们准备好了相应的数据,无需等待,随要随到。如果我们调用这个服务不是很频繁,一个协程足够满足我们的需求了。但如果我们需要大量访 问,怎么办?我们可以用下面介绍的多路复用技术,启动若干生成器,再将其整合成一个大的服务。

调用生成器,可以返回一个“服务”。可以用在持续获取数据的场合。用途很广泛,读取数据,生成ID,甚至定时器。这是一种非常简洁的思路,将程序并发化。

多路复用

多路复用是让一次处理多个队列的技术。Apache使用处理每个连接都需要一个进程,所以其并发性能不是很好。而Nginx使用多路复用的技术,让一 个进程处理多个连接,所以并发性能比较好。同样,在协程的场合,多路复用也是需要的,但又有所不同。多路复用可以将若干个相似的小服务整合成一个大服务。

那么让我们用多路复用技术做一个更高并发的随机数生成器吧。

// 函数rand_generator_3 ,返回通道(Channel)

funcrand_generator_3() chan int {

         // 创建两个随机数生成器服务

         rand_generator_1 := rand_generator_2()

         rand_generator_2 := rand_generator_2()

         //创建通道

         out := make(chan int)

         //创建协程

         go func() {

                   for {

                            //读取生成器1中的数据,整合

                            out <-<-rand_generator_1

                   }

         }()

         go func() {

                   for {

                            //读取生成器2中的数据,整合

                            out <-<-rand_generator_2

                   }

         }()

         return out

}

上面是使用了多路复用技术的高并发版的随机数生成器。通过整合两个随机数生成器,这个版本的能力是刚才的两倍。虽然协程可以大量创建,但是众多协程还是会 争抢输出的通道。Go语言提供了Select关键字来解决,各家也有各家窍门。加大输出通道的缓冲大小是个通用的解决方法。

多路复用技术可以用来整合多个通道。提升性能和操作的便捷。配合其他的模式使用有很大的威力。

Future技术

Future是一个很有用的技术,我们常常使用Future来操作线程。我们可以在使用线程的时候,可以创建一个线程,返回Future,之后可以通过它等待结果。  但是在协程环境下的Future可以更加彻底,输入参数同样可以是Future的。

调用一个函数的时候,往往是参数已经准备好了。调用协程的时候也同样如此。但是如果我们将传入的参 数设为通道,这样我们就可以在不准备好参数的情况下调用函数。这样的设计可以提供很大的自由度和并发度。函数调用和函数参数准备这两个过程可以完全解耦。 下面举一个用该技术访问数据库的例子。

//一个查询结构体

typequery struct {

         //参数Channel

         sql chan string

         //结果Channel

         result chan string

}

//执行Query

funcexecQuery(q query) {

         //启动协程

         go func() {

                   //获取输入

                   sql := <-q.sql

                   //访问数据库,输出结果通道

                   q.result <- "get" + sql

         }()

}

funcmain() {

         //初始化Query

         q :=

                   query{make(chan string, 1),make(chan string, 1)}

         //执行Query,注意执行的时候无需准备参数

         execQuery(q)

         //准备参数

         q.sql <- "select * fromtable"

         //获取结果

         fmt.Println(<-q.result)

}

        上面利用Future技术,不单让结果在Future获得,参数也是在Future获取。准备好参数后,自动执行。Future和生成器的区别在 于,Future返回一个结果,而生成器可以重复调用。还有一个值得注意的地方,就是将参数Channel和结果Channel定义在一个结构体里面作为 参数,而不是返回结果Channel。这样做可以增加聚合度,好处就是可以和多路复用技术结合起来使用。

        Future技术可以和各个其他技术组合起来用。可以通过多路复用技术,监听多个结果Channel,当有结果后,自动返回。也可以和生成器组合使用,生 成器不断生产数据,Future技术逐个处理数据。Future技术自身还可以首尾相连,形成一个并发的pipe filter。这个pipe filter可以用于读写数据流,操作数据流。

        Future是一个非常强大的技术手段。可以在调用的时候不关心数据是否准备好,返回值是否计算好的问题。让程序中的组件在准备好数据的时候自动跑起来。

并发循环

       循环往往是性能上的热点。如果性能瓶颈出现在CPU上的话,那么九成可能性热点是在一个循环体内部。所以如果能让循环体并发执行,那么性能就会提高很多。

要并发循环很简单,只有在每个循环体内部启动协程。协程作为循环体可以并发执行。调用启动前设置一个计数器,每一个循环体执行完毕就在计数器上加一个元素,调用完成后通过监听计数器等待循环协程全部完成。

//建立计数器

sem :=make(chan int, N);

//FOR循环体

for i,xi:= range data {

         //建立协程

    go func (i int, xi float) {

        doSomething(i,xi);

                   //计数

        sem <- 0;

    } (i, xi);

}

// 等待循环结束

for i := 0; i < N; ++i { <-sem }

       上面是一个并发循环例子。通过计数器来等待循环全部完成。如果结合上面提到的Future技术的话,则不必等待。可以等到真正需要的结果的地方,再去检查数据是否完成。

        通过并发循环可以提供性能,利用多核,解决CPU热点。正因为协程可以大量创建,才能在循环体中如此使用,如果是使用线程的话,就需要引入线程池之类的东西,防止创建过多线程,而协程则简单的多。

ChainFilter技术

      前面提到了Future技术首尾相连,可以形成一个并发的pipe filter。这种方式可以做很多事情,如果每个Filter都由同一个函数组成,还可以有一种简单的办法把他们连起来。

由于每个Filter协程都可以并发运行,这样的结构非常有利于多核环境。下面是一个例子,用这种模式来产生素数。

// Aconcurrent prime sieve

packagemain

// Sendthe sequence 2, 3, 4, ... to channel 'ch'.

funcGenerate(ch chan<- int) {

         for i := 2; ; i++ {

                  ch<- i // Send 'i' to channel 'ch'.

         }

}

// Copythe values from channel 'in' to channel 'out',

//removing those divisible by 'prime'.

funcFilter(in <-chan int, out chan<- int, prime int) {

         for {

                   i := <-in // Receive valuefrom 'in'.

                   if i%prime != 0 {

                            out <- i // Send'i' to 'out'.

                   }

         }

}

// Theprime sieve: Daisy-chain Filter processes.

funcmain() {

         ch := make(chan int) // Create a newchannel.

         go Generate(ch)      // Launch Generate goroutine.

         for i := 0; i < 10; i++ {

                   prime := <-ch

                   print(prime, "\n")

                   ch1 := make(chan int)

                   go Filter(ch, ch1, prime)

                   ch = ch1

         }

}

        上面的程序创建了10个Filter,每个分别过滤一个素数,所以可以输出前10个素数。   

        Chain-Filter通过简单的代码创建并发的过滤器链。这种办法还有一个好处,就是每个通道只有两个协程会访问,就不会有激烈的竞争,性能会比较好。

共享变量

        协程之间的通信只能够通过通道。但是我们习惯于共享变量,而且很多时候使用共享变量能让代码更简洁。比如一个Server有两个状态开和关。其他仅仅希望获取或改变其状态,那又该如何做呢。可以将这个变量至于0通道中,并使用一个协程来维护。

下面的例子描述如何用这个方式,实现一个共享变量。

//共享变量有一个读通道和一个写通道组成

typesharded_var struct {

         reader chan int

         writer chan int

}

//共享变量维护协程

funcsharded_var_whachdog(v sharded_var) {

         go func() {

                   //初始值

                   var value int = 0

                   for {

                            //监听读写通道,完成服务

                            select {

                            case value =<-v.writer:

                            case v.reader <-value:

                            }

                   }

         }()

}

funcmain() {

         //初始化,并开始维护协程

         v := sharded_var{make(chan int),make(chan int)}

         sharded_var_whachdog(v)

         //读取初始值

         fmt.Println(<-v.reader)

         //写入一个值

         v.writer <- 1

         //读取新写入的值

         fmt.Println(<-v.reader)

}

        这样,就可以在协程和通道的基础上实现一个协程安全的共享变量了。定义一个写通道,需要更新变量的时候,往里写新的值。再定义一个读通道,需要读的时候,从里面读。通过一个单独的协程来维护这两个通道。保证数据的一致性。

        一般来说,协程之间不推荐使用共享变量来交互,但是按照这个办法,在一些场合,使用共享变量也是可取的。很多平台上有较为原生的共享变量支持,到底用那种 实现比较好,就见仁见智了。另外利用协程和通道,可以还实现各种常见的并发数据结构,如锁等等,就不一一赘述。

协程泄漏

        协程和内存一样,是系统的资源。对于内存,有自动垃圾回收。但是对于协程,没有相应的回收机制。会不会若干年后,协程普及了,协程泄漏和内存泄漏一样成为 程序员永远的痛呢?一般而言,协程执行结束后就会销毁。协程也会占用内存,如果发生协程泄漏,影响和内存泄漏一样严重。轻则拖慢程序,重则压垮机器。

        C和C++都是没有自动内存回收的程序设计语言,但只要有良好的编程习惯,就能解决规避问题。对于协程是一样的,只要有好习惯就可以了。

        只有两种情况会导致协程无法结束。一种情况是协程想从一个通道读数据,但无人往这个通道写入数据,或许这个通道已经被遗忘了。还有一种情况是程想往一个通道写数据,可是由于无人监听这个通道,该协程将永远无法向下执行。下面分别讨论如何避免这两种情况。

        对于协程想从一个通道读数据,但无人往这个通道写入数据这种情况。解决的办法很简单,加入超时机制。对于有不确定会不会返回的情况,必须加入超时,避免出 现永久等待。另外不一定要使用定时器才能终止协程。也可以对外暴露一个退出提醒通道。任何其他协程都可以通过该通道来提醒这个协程终止。

对于协程想往一个通道写数据,但通道阻塞无法写入这种情况。解决的办法也很简单,就是给通道加缓冲。但前提是这个通道只会接收到固定数目的写入。比方说, 已知一个通道最多只会接收N次数据,那么就将这个通道的缓冲设置为N。那么该通道将永远不会堵塞,协程自然也不会泄漏。也可以将其缓冲设置为无限,不过这 样就要承担内存泄漏的风险了。等协程执行完毕后,这部分通道内存将会失去引用,会被自动垃圾回收掉。

funcnever_leak(ch chan int) {

         //初始化timeout,缓冲为1

         timeout := make(chan bool, 1)

         //启动timeout协程,由于缓存为1,不可能泄露

         go func() {

                   time.Sleep(1 * time.Second)

                   timeout <- true

         }()

         //监听通道,由于设有超时,不可能泄露

         select {

         case <-ch:

                   // a read from ch hasoccurred

         case <-timeout:

                   // the read from ch has timedout

         }

}

        上面是个避免泄漏例子。使用超时避免读堵塞,使用缓冲避免写堵塞。

        和内存里面的对象一样,对于长期存在的协程,我们不用担心泄漏问题。一是长期存在,二是数量较少。要警惕的只有那些被临时创建的协程,这些协程数量大且生 命周期短,往往是在循环中创建的,要应用前面提到的办法,避免泄漏发生。协程也是把双刃剑,如果出问题,不但没能提高程序性能,反而会让程序崩溃。但就像 内存一样,同样有泄漏的风险,但越用越溜了。

并发模式之实现

在并发编程大行其道的今天,对协程和通道的支持成为各个平台比不可少的一部分。虽然各家有各家的叫法,但都能满足协程的基本要求—并发执行和可大量创建。笔者对他们的实现方式总结了一下。

        下面列举一些已经支持协程的常见的语言和平台。

GoLang 和Scala作为最新的语言,一出生就有完善的基于协程并发功能。Erlang最为老资格的并发编程语言,返老还童。其他二线语言则几乎全部在新的版本中加入了协程。

令人惊奇的是C/C++和Java这三个世界上最主流的平台没有在对协程提供语言级别的原生支持。他们都背负着厚重的历史,无法改变,也无需改变。但他们还有其他的办法使用协程。

Java平台有很多方法实现协程:

        · 修改虚拟机:对JVM打补丁来实现协程,这样的实现效果好,但是失去了跨平台的好处

        · 修改字节码:在编译完成后增强字节码,或者使用新的JVM语言。稍稍增加了编译的难度。

        · 使用JNI:在Jar包中使用JNI,这样易于使用,但是不能跨平台。

        · 使用线程模拟协程:使协程重量级,完全依赖JVM的线程实现。

        其中修改字节码的方式比较常见。因为这样的实现办法,可以平衡性能和移植性。最具代表性的JVM语言Scale就能很好的支持协程并发。流行的Java Actor模型类库akka也是用修改字节码的方式实现的协程。

        对于C语言,协程和线程一样。可以使用各种各样的系统调用来实现。协程作为一个比较高级的概念,实现方式实在太多,就不讨论了。比较主流的实现有libpcl, coro,lthread等等。

        对于C++,有Boost实现,还有一些其他开源库。还有一门名为μC++语言,在C++基础上提供了并发扩展。

        可见这种编程模型在众多的语言平台中已经得到了广泛的支持,不再小众。如果想使用的话,随时可以加到自己的工具箱中。

结语 

        本文探讨了一个极其简洁的并发模型。在只有协程和通道这两个基本元件的情况下。可以提供丰富的功能,解决形形色色实际问题。而且这个模型已经被广泛的实 现,成为潮流。相信这种并发模型的功能远远不及此,一定也会有更多更简洁的用法出现。或许未来CPU核心数目将和人脑神经元数目一样多,到那个时候,我们 又要重新思考并发模型了。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Go语言并发之美:解释其中内核、外延 的相关文章

  • 在 OSX 上交叉编译 Go?

    我正在尝试在 OSX 上交叉编译 go 应用程序以构建适用于 Windows 和 Linux 的二进制文件 我已经阅读了网上能找到的所有内容 我发现的最接近的例子已经发布在 除了疯狂邮件列表上许多未完成的讨论之外 http solovyov
  • 无法在 Golang 中导入本地模块

    我正在尝试导入本地模块 但无法使用以下命令导入它go mod 我最初使用以下方式构建了我的项目go mod init github com AP Ch2 GOMS 注意我的环境是go1 14我使用 VSCode 作为我的编辑器 这是我的文件
  • 带有导出字段的私有类型

    在 Go 教程的第二天有这样的练习 为什么拥有带有导出字段的私有类型会很有用 例如 package geometry type point struct X Y int name string 请注意point是小写的 因此不会导出 而字段
  • 命名和未命名类型

    问题 我最近开始阅读Golang规格手册 https golang org ref spec并陷入试图理解的困境有名和无名类型在相关部分 https golang org ref spec Types 我来自动态语言 这让我有点头疼 手册指
  • 模块路径格式错误...第一个路径元素中缺少点

    我有一个包含 2 个不同可执行文件的项目 每个可执行文件都有自己的依赖项以及对根的共享依赖项 如下所示 Root gt server gt main go gt someOtherFiles go gt go mod gt go sum g
  • 限制 FormFile 中的文件大小

    我让用户使用 FormFile 上传文件 我应该在什么时候检查文件大小是否太大 当我做 file header fileErr r FormFile file 文件对象已经创建 那么我是否已经产生了读取整个文件的成本 https golan
  • 在 Go 中读取请求负载?

    我正在使用文件上传器 需要请求负载中的详细信息来裁剪它 func Upload w http ResponseWriter r http Request reader err r MultipartReader if err nil htt
  • 无法封送,(实现encoding.BinaryMarshaler)。具有多个对象的 go-redis Sdd

    我有下面一段代码 我试图将一个数组添加到 redis 集中 但它给了我一个错误 package main import encoding json fmt github com go redis redis type Info struct
  • 从 Go Slice 中选择一个随机值

    情况 我有一些值 需要从中随机选择一个值 然后我想将它与固定字符串连接起来 到目前为止 这是我的代码 func main create the reasons slice and append reasons to it reasons m
  • Bash脚本无法执行Go命令

    我正在尝试编写一个 bash 脚本来自动在不同的目录中运行 go get install 相关部分在这里 cd web go get cd web go install cd services go get cd services go i
  • 如何自定义解析错误的 HTTP 400 响应?

    我编写了一个 REST API 服务 要求所有响应均为 JSON 但是 当 Go HTTP 请求解析器遇到错误时 它会返回 400 作为纯文本响应 而不会调用我的处理程序 例子 gt curl i H Authorization Basic
  • vscode 中的调试不会在断点处停止,调试器启动时显示“无法找到文件...”

    乌班图 vscode 1 62 1 去1 17 3 vscode go 扩展 v0 29 0 深入研究 v1 7 1 我是 vscode 和 Go 的新手 我有多年在 Eclipse 中调试 Java 应用程序的经验 我构建了一个小型多模块
  • 如何在 Go 中表示可选字符串?

    我希望建模一个可以有两种可能形式的值 不存在或字符串 执行此操作的自然方法是Maybe String or Optional
  • 无法通过键获取 Gorilla 会话值

    我无法通过这种方式从会话中获取价值 它是nil session initSession r valWithOutType session Values key 完整代码 package main import fmt github com
  • 如何将未知字段类型的数据解组为 JSON

    我有这些 结构 type Results struct Gender string json gender Name struct First string json first Last string json last json nam
  • nsq 无法通过连接到 nsqlookupd 来消费消息

    我尝试使用 docker compose 来运行 nsq docker compose yml如下 version 3 services nsqlookupd image nsqio nsq command nsqlookupd ports
  • 我可以根据我正在构建的操作系统导入 Golang 包吗?

    假设我有一个基于哪个操作系统的 go 项目 在某些情况下是哪个发行版 我想使用 Systemd 客户端包 Upstart 客户端包 sysv 客户端包 launchd 客户端包 是否可以有选择地导入每个包 以便我只导入我正在构建的每个操作系
  • 在处理程序之后访问 HTTP 请求上下文

    在我的日志记录中间件 链中的第一个 中 我需要访问一些在链下游的某些身份验证中间件中编写的上下文 并且仅在处理程序本身执行之后 旁注 需要首先调用日志记录中间件 因为我需要记录请求的持续时间 包括在中间件中花费的时间 此外 当权限不足时 身
  • Go 无法推断赋值中的类型:“non-name on left side of :=”

    该片段按预期工作play golang org p VuCl OKMav http play golang org p VuCl OKMav i 10 next 11 prev i i next 然而这个几乎相同的片段给出了non name
  • 如何从非英语字符串解析go中的月份

    我想将以下字符串解析为 go 中的日期 This item will be released on March 9 2014 我跟着this https stackoverflow com questions 14106541 go par

随机推荐

  • 基于马尔可夫链的写作机器人软件

    数据结构课设 二 作业要求 设计并实现一个基于马尔可夫链的写作机器人软件 软件通过对素材文本的学习 建立词库以及单词的马尔可夫链 然后使用所建立的单词马尔可夫链配合随机数选择 自动生成一段文字 分析 由于不太清楚老师的具体要求 所以在网上找
  • 微信小程序如何根据不同用户切换不同TabBar

    现有需求 小程序用户有三种身份 公众 运维人员 领导 根据不同用户身份显示不同的tabbar 众所周知微信小程序全局文件app json里面的 tabBar 里面的list只能放置2 5个 要想实现3个tabbar 必须得复用tabbar
  • 回归分析详解及matlab实现

    当人们对研究对象的内在特性和各因素间的关系有比较充分的认识时 一般用机理分析方法建立数学模型 如果由于客观事物内部规律的复杂性及人们认识程度的限制 无法分析实际对象内在的因果关系 建立合乎机理规律的数学模型 那么通常的办法是搜集大量数据 基
  • 惊!STM32 蓝牙串口模块(H21/JDY-31) 竟如此简单!

    惊 STM32 蓝牙串口模块 H21 JDY 31 竟如此简单 文章日志 1 写于2022 08 19 文章目录 1 认识蓝牙串口模块 2 困扰我很久的实验竟如此简单 3 一些现象的思考 1 认识蓝牙串口模块 JDY 31 蓝牙基于蓝牙 3
  • sql服务器显示空白,sql服务器空白

    sql服务器空白 内容精选 换一换 登录Windows操作系统的裸金属服务器时 需使用密码方式登录 因此 用户需先根据创建裸金属服务器时使用的密钥文件 获取该裸金属服务器初始安装时系统生成的管理员密码 Administrator帐户或Clo
  • 剑指 Offer 50. 第一个只出现一次的字符

    剑指 Offer 50 第一个只出现一次的字符 题目 题目链接 具体代码 集合缓存法 索引比较法 题目 题目链接 https leetcode cn com problems di yi ge zhi chu xian yi ci de z
  • linux线程私有数据详解

    在单线程程序中 函数经常使用全局变量或静态变量 这是不会影响程序的正确性的 但如果线程调用的函数使用全局变量或静态变量 则很可能引起编程错误 因为这些函数使用的全局变量和静态变量无法为不同的线程保存各自的值 而当同一进程内的不同线程几乎同时
  • Qt:提示框类型使用

    一 默认提示框 注意 使用前需要导入相应库 include
  • 私有构造函数

    通常我们都将构造函数的声明置于public区段 假如我们将其放入private区段中会发生什么样的后果 没错 我也知道这将会使构造函数成为私有的 这意味着什么 我们知道 当我们在程序中声明一个对象时 编译器为调用构造函数 如果有的话 而这个
  • 天线的π型电路作用

    天线的 型电路作用 天线阻抗匹配的意义 常见的WiFi或者蓝牙天线都有 型电路 主要是做阻抗匹配的 起初以为是解决EMI的问题 后来才知是阻抗匹配的 其目的就是实现功率的最大传输 让天线的指标达到最好状态 阻抗匹配的基本原理 以纯电阻阻抗电
  • hiredis发布/订阅示例

    转 http www xuebuyuan com 950148 html include
  • 什么是obj文件?

    百度百科 程序编译时生成的中间代码文件 目标文件 一般是程序编译后的二进制文件 再通过链接器 LINK EXE 和资源文件链接就成可执行文件了 OBJ只给出了程序的相对地址 而可执行文件是绝对地址 1 这个问题不是很简单 你只看到了文件从源
  • rename函数的用法

    import pandas as pd df pd DataFrame A 1 2 3 B 4 5 6 print df df1 df rename columns A a B c print df1 结果 A B 0 1 4 1 2 5
  • tensorflow的Data Pipeline系列教程(一)——Dataset类的属性即常用方法

    前言 在tensorflow中 训练数据常常需要经过随机打乱 分成一个一个的batch来进行训练 当然有很多的方式可以完成 比如我们可以通过传统的python方法构建迭代器 我们也可以使用其它的一些方法 但是tensorflow本身提供了强
  • 2.管理者的分类与角色

    管理者及其分类 什么是管理者 管理者是组织中那些指挥别人活动的人 管理者工作绩效的好坏直接关系着组织的兴衰成败 管理者处于组织中的不同层次 其头衔也各式各样 但他们工作具有一个共同的特征 即都是通过协调他人的努力来是组织活动更加有效并实现组
  • 328. 奇偶链表-链表拆分合并

    一 题目描述 给定单链表的头节点 head 将所有索引为奇数的节点和索引为偶数的节点分别组合在一起 然后返回重新排序的列表 第一个节点的索引被认为是 奇数 第二个节点的索引为 偶数 以此类推 请注意 偶数组和奇数组内部的相对顺序应该与输入时
  • 坏苹果是团队的毒药

    最近一期的 美国生活 采访了WillFelps 他是华盛顿大学的一位教授 曾经组织过一次社会学实验来证明 坏苹果 的出奇强大的影响力 译者注 美国生活 This American Life 是一档叙事类的广播节目 每周一期 在超过500家电
  • ubuntu10.04和12.04 上cpan的安装步骤及常见问题解决方法

    在我们用 perl 编写程序的时候 会要用到很多的 perl module 这种情况下就需要安装程序所需要的 module 然而这些module通常都是在www cpan org网站上面抓的 所以我们可以通过安装cpan来安装perl程序需
  • mysql 备忘录

    文章目录 rownum 版本 DDL DML insert insert into x with recursive mysql8 0 临时表 常用 存储过程 数据导出 导入 mysqldump load data csv 比 insert
  • Go语言并发之美:解释其中内核、外延

    多核处理器越来越普及 那有没有一种简单的办法 能够让我们写的软件释放多核的威力 答案是 Yes 随着Golang Erlang Scale等为并发设计的程序语言的兴起 新的并发模式逐渐清晰 正如过程式编程和面向对象一样 一个好的编程模式需要