Go语言的运行机制&程序是怎么跑起来的

1 篇文章 1 订阅
订阅专栏

学习Golang有一段时间了,自己看着各种教程也码了些demo。其实接触了这么多语言,当因为工作、项目、兴趣所驱在短时间切换一门编程语言时,并不会太难上手,甚至会对了解一些很雷同的基础语法感到枯燥,但这是必经之路。对于一个技术爱好者而言,技术广度技术深度技术新特性等往往是最好的兴奋剂。今天这篇文章主要结合最近的资料学习,对Go语言的运行机制及Go程序的运作进行一些稍微深入的分析及总结,对Go的启动执行流程建立简单的宏观认知~

为什么Go语言适合现代的后端编程环境?

  • 服务类应用以API居多,IO密集型,且网络IO最多;
  • 运行成本低,无VM。网络连接数不多的情况下内存占用低;
  • 强类型语言,易上手,易维护;

为什么适合基础设施?

  • k8setcdistiodocker已经证明了Go的能力

一、理解可执行文件

1. 基本实验环境准备

使用docker构建基础环境

FROM centos
RUN yum install golang -y \
   && yum install dlv -y \ 
   && yum install binutils -y \ 
   && yum install vim -y \ 
   && yum install gdb -y
# docker build -t test .
# docker run -it --rm test bash

2. Go语言的编译过程

Go程序的编译过程:文本 -> 编译 -> 二进制可执行文件
image-20210923171738035
编译:文本代码 -> 目标文件(.o, .a)

链接:将目标文件合并为可执行文件

使用go build -x xxx.go可以观察这个过程
查看Go程序的编译过程

3. 不同系统的可执行文件规范

可执行文件在不同的操作系统规范不一样
不同系统的可执行文件
Linux的可执⾏⽂件ELF(Executable and Linkable Format) 为例,ELF 由⼏部分构成:

  • ELF header
  • Section header
  • Sections

操作系统执行可执行文件的步骤(Linux为例):
Linux可执行文件的执行过程

4. 如何寻找Go进程的入口

通过entry point找到 Go进程的执⾏⼊⼝,使⽤readelf。进一步找到Go进程要从哪里启动了~
Go进程启动的入口

二、Go进程的启动与初始化

1. 计算机如何执⾏我们的程序

计算机对于程序的执行
CPU⽆法理解⽂本,只能执⾏⼀条⼀条的⼆进制机器码指令,每次执⾏完⼀条指令,pc寄存器就指向下⼀条继续执⾏。

在 64 位平台上pc 寄存器 = rip

计算机会自上而下,依次执行汇编指令:
计算机对于程序的执行

2. Runtime是什么&Go语言的Runtime

Go 语⾔是⼀⻔有runtime的语⾔,那么runtime是什么?
Runtime定义
可以认为runtime是为了实现额外的功能,⽽在程序运⾏时⾃动加载/运⾏的⼀些模块。

Go语言中,运行时操作系统程序员定义代码之间的关系如下图:
Go运行时、操作系统、代码的关系
在Go语言中,runtime主要包括:

  • Scheduler:调度器管理所有的 G,M,P,在后台执⾏调度循环
  • Netpoll:⽹络轮询负责管理⽹络 FD 相关的读写、就绪事件
  • Memory Management:当代码需要内存时,负责内存分配⼯作
  • Garbage Collector:当内存不再需要时,负责回收内存

这些模块中,最核⼼的就是 Scheduler,它负责串联所有的runtime 流程。

通过 entry point 找到 Go 进程的执⾏⼊⼝:

Go进程的执⾏⼊⼝

runtime.rt0_go的相关处理过程:

  • 开始执行用户main函数(从这里开始进入调度循环)
  • 初始化内置数据结构
  • 获取CPU核心数
  • 全局m0g0初始化
  • argcargv处理

m0为Go程序启动后创建的第一个线程

三、调度组件与调度循环

1. Go的生产-消费流程概述

每当写下:

go func() {
  println("hello alex")
}()

的时候,到底发生了什么?这里其实就是向runtime提交了一个计算任务,func里面所裹挟的代码,就是这个计算任务的基本内容~

Go的调度流程本质上就是一个生产-消费流程,下图为生产消费的概况流程:

Go的生产和消费流程

  • 右边的生产者就是每次go func() {}的时候提交的任务;
  • 中间的为队列,发送的任务会被打包成一个协程G,即为goroutine
  • goroutine会进入到这个队列,而另一端进行消费的就是线程,线程是在循环里面执行消费的操作的;
  • 中间的队列主要会分为2部分,分别是本地队列全局队列

2. Go的调度组件P、G、M结构

先整体给P、G、M下一个定义:

  • G:goroutine,⼀个计算任务。由需要执⾏的代码和其上下⽂组成,上下⽂包括:当前代码位置,栈顶、栈底地址,状态等。
  • M:machine,系统线程,执⾏实体,想要在 CPU 上执⾏代码,必须有线程,与 C 语⾔中的线程相同,通过系统调⽤ clone 来创建。
  • P:processor,虚拟处理器,M 必须获得 P 才能执⾏代码,否则必须陷⼊休眠(后台监控线程除外),你也可以将其理解为⼀种 token,有这个 token,才有在物理 CPU 核⼼上执⾏的权⼒。

本节的内容全部介绍完后回顾这几个概念,就会觉得相对好理解一些~

整体的结构图如下:

调度组件

  • 右边的蓝色、黄色、绿色的M即为线程,大部分线程是一直在执行一个调度循环的,调度循环简单就是指线程要去左边的任务队列里(local run queue & global run queue)把任务拿出来然后执行的反复的操作;
  • 当然在整个过程中,线程是按需创建的,因此有一部分线程可能是空闲的,这些线程会被放在一个叫做midle的队列中来进行管理,当没有可用的空闲线程时候就会在midle里面寻找使用;
  • 我们可以看到上图中,除了local run queue(本地队列)global run queue(全局队列),还有一个runnext的结构,而runnextlocal run queue 本质上都是为了解决程序的局部性问题**(程序的局部性原理:最近调用的一次代码很有很可能会马上被再一次调用,整体分为代码的局部性和数据的局部性)** ,我们一般不希望所有的生产都进入到全局的global run queue中;
  • 如果所有的线程消费的都是global run queue的话,那么还需要进行额外加锁设计。这就是为什么会分为local run queueglobal run queue的原因。

3. Go的生产-消费详解

goroutine的生产端(runnextlocal run queueglobal run queue的过程)
goroutine的生产端

  • 左上角会创建一个goroutine,而这个goroutine会创建一个runtime,即通过runtime.newproc生成一个G;
  • 对于G的队列而言,runnext的优先级是最高的,首先会进入到runnext中;
  • 但新的G进去,有可能会导致老的G被挤出,此时需要进行善后工作,老的G会进入到本地队列,而如果本地队列也已经满了的话,就会把本地队列拿出一半,塞给全局队列,以此循环;
  • 注意:runnext本质上并不是队列,而是一个含有一个元素的指针,为了方便理解,将其与另外的本地队列(本质上是一个数组,且只有256的长度)全局队列(本质上是一个链表)叫法一致。

goroutine的消费端
goroutine的消费端

  • 消费端本质上就是多个线程在反复执行一个循环,这个循环是从队列里面取值,上图右边的蓝色块指的就是标准的调度循环的流程,即runtime里面的4个函数:runtime.scheduleruntime.executeruntime.goexitruntime.gogo;
  • 图中红色的区域是垃圾回收gc相关的逻辑,schedule左边的3个黄色框,都为获取G的函数,如果schedule左边的任意一个函数返回一个G给schedule,右边的循环就会一直执行;
  • 在这些函数中,globalrunqget/61指的就是会定期61次执行,去全局队列里面检索获取一个G,防止在全局队列里面的G过度延迟;
  • 如果全局的G没有获取到,或者当前不需要获取全局的G,就会从本地队列进行获取(优先获取runnext),而本地队列的获取就是通过runqget这个函数做到的;
  • 如果还是没有获取到G的话,就会去执行findrunnable函数,这个函数整体分为上下两部分,分别叫topstoptop部分的函数功能,主要就是再次尝试依次从本地队列->全局队列获取G,如果依然获取不到,就使用netpoll进行网络轮询情况的查看,如果在这里能找到G,就将G放在全局队列里面,如果依然获取不到,就使用runqsteal从其他的P中偷一半G回来,这个有点像Work stealing 的原理( runqsteal -> runqgrab);
  • 如果执行完整个top部分依然获取不到G,就说明M没有机会得到执行了,那么就开始执行stop部分,即线程的休眠流程,但在stopm执行之前,还是会再次检查一遍G的存在,确认无误后,就会将线程休眠。
  • 需要注意的是:M 执⾏调度循环时,必须与⼀个 P 绑定;所有global操作均需要加锁。

下面再单独将右边的调度循环过程摘出来描述一下:
调度循环的单次流程

  • 在上面的调度循环中,最重要的就是schedule,它可以从相关的语言中去寻找正在执行的任务;
  • schedule获取到G后,就进行execute流程(执行go的代码),gogo会把拿到的G的现场回复出来,从PC寄存器开始继续执行,goexit会结束当前的一次流程,并缓存相关的G结构体资源,然后回到schedule继续执行循环;
  • 在调度循环的过程中,会存在一个P.scheditick的字段,用来记录调度循环已经执行了多少次,用于globrunnqget/61等判定中。当执行到execute的时候,P.scheditick就会+1

前面介绍的就是调度循环及调度组件的内容,但Go仅仅能够处理正常情况是不行的,如果程序中有阻塞的话,需要避免线程阻塞~

四、处理阻塞

1. Go语言中常见的阻塞情况

channel
阻塞场景1

time.Sleep
阻塞场景2

网络读
阻塞场景3

网络写
阻塞场景4

select语句
阻塞场景5


阻塞场景6

以上的6种阻塞,阻塞调度循环,⽽是会把 goroutine 挂起所谓的挂起,其实让G 先进某个数据结构,待 ready 后再继续执⾏,不会占⽤线程

这时候,线程会进⼊ schedule,继续消费队列,执⾏其它的 G

2. 各类阻塞中G是如何挂起的

阻塞的各类情况

  1. channel发送:如果阻塞了,会有一个sendq等待队列,将G打包为sudog的数据结构,塞在了等待结构中;
  2. channel接收:如果阻塞了,会有一个recvq等待队列,将G打包为sudog的数据结构,塞在了等待结构中;
  3. 链接的写阻塞:G会挂在底层pollDescwg中;
  4. 链接的读阻塞:G会挂在底层pollDescrg中;
  5. select阻塞:以图中的3个channel为例,会有3个sendq或者是recvq队列,G则打包为sudog挂在这些队列的尾部;
  6. time.Sleep阻塞:将G挂在timer结构的一个参数上。

由于锁的阻塞相对特殊,单独拿出来说。
锁的阻塞

  • 和前面的集中阻塞情况相似的是,锁的阻塞依然会将G打包为sudog,会停留在树堆的结构中,树堆是一个二叉平衡树,且其中的每一个节点就是一个链表

根据上面的介绍,我们可以看到,有些挂起等待结构是sudog而有些是G,为什么会这样呢?

因为,⼀个 G 可能对应多个 sudog,⽐如⼀个 G 会同时 select 多个channel,在runtime中有对这里解读的注释:

sudog与G的解读

3. runtime无法处理的阻塞

CGO
CGO

阻塞在syscall
阻塞syscall
执⾏ c 代码,或者阻塞在 syscall 上时,必须占⽤⼀个线程

4. sysmon

sysmon: system monitor

sysmon在后台具有⾼优先级,在专有线程中执⾏,不需要绑定 P 就可以执⾏。

sysmon主要有3个作用:

  1. checkdead —> 用于检查是否当前的所有线程都被阻塞住了,如果所有线程死锁,说明程序写的有问题,需要直接崩溃提示。对于网络应用而言,一般不会触发。常见的误解是:这个可以检查死锁
  2. netpoll —> 将G插入到全局队列里面;
  3. retake —> 如果是 syscall 卡了很久,那就把 PM上剥离(handoffp);在go1.14以后,如果是⽤户 G 运⾏很久了,那么发信号抢占

go1.14:SIGURG抢占

五、调度器的发展历史

调度器的发展历史

六、知识点总结

1. 可执⾏⽂件 ELF:

  • 使⽤ go build -x 观察编译和链接过程
  • 通过 readelf -H 中的 entry 找到程序⼊⼝
  • dlv 调试器b *entry_addr 找到代码位置

2. 启动流程:

  • 处理参数 -> 初始化内部数据结构 -> 主线程 -> 启动调度循环

3. Runtime 构成:

  • SchedulerNetpoll内存管理垃圾回收

4. GMP:

  • M,任务消费者;G,计算任务;P,可以使⽤ CPUtoken

5. 队列:

  • P 的本地 runnext 字段 -> P 的 local run queue ->global run queue,多级队列减少锁竞争

6. 调度循环:

  • 线程 M 在持有 P 的情况下不断消费运⾏队列中的 G 的过程。

7.处理阻塞:

  • 可以接管的阻塞:channel 收发加锁⽹络连接读/写select
  • 不可接管的阻塞:syscallcgo,⻓时间运⾏需要剥离 P 执⾏

8. sysmon:

  • ⼀个后台⾼优先级循环,执⾏时不需要绑定任何的 P ,负责:
  • 检查是否已经没有活动线程,如果是,则崩溃;
  • 轮询 netpoll
  • 剥离在 syscall 上阻塞的 M 的 P ;
  • 发信号,抢占已经执⾏时间过⻓的 G。
Go语言安装配置运行
weixin_49411859的博客
05-07 667
安装Go 首先去官网下载Golang https://golang.google.cn/dl/ 下载好后傻瓜式安装,安装完成后,打开cmd命令行输入go version测试是否安装成功 配置GOPATH 首先新建一个文件夹作为工作区,可以是在任意盘中,前提是目录必须为英文,这里我就在go的文件夹下新建一个workplace 文件夹 复制该文件目录地址后,按住win + Pause Break键打开控制面板 再点击高级系统设置 点击环境变量 在系统变量中新建一个GOPATH变量,值为刚才新建的文件夹
Go语言调用其它程序并获得程序输出的方法
12-25
本文实例讲述了Go语言调用其它程序并获得程序输出的方法。分享给大家供大家参考。具体实现方法如下: 代码如下:package main import (  “exec” // “os/exec” in go1  “fmt” ) func main(){  cmd := exec....
Go语言核心知识点和原理详解
u012588879的专栏
12-08 1057
go语言的核心知识点和核心原理讲解,比较全面地了解go的内部机制
Go数据结构的底层原理(图文详解)
一枚coder的精进(荆棘)之路
04-07 977
Go程序开发进阶保姆级教程,图文详解Go数据结构(字符串、数组、切片、map、结构体、接口)的底层原理及应用
1、go程序运行方式
最新发布
m0_69711535的博客
05-07 130
直接运行.exe文件----如果我们先编译生成了可执行文件,那么我们可以将该可执行文件拷贝到没有go开发环境的机器上,仍然可以直接运行。:生成.exe文件----编译时,会将程序运行依赖的库文件包含在.exe(可执行文件)中,所以,可执行文件变大了很多。----不会生成可执行文件,且执行的机器上必须配置好go开发环境。go build 源程序。go run 源程序
Go语言 Go的执行原理以及Go的命令
Waller_的博客
09-04 812
比如,本机安装Go语言的版本是1.x,那么go get命令会在该代码包的远程仓库中寻找名为“go1”的标签或者分支。如果不加这个-u标记,执行go get一个已有的代码包,会发现命令什么都不执行。这种情况下,go build 命令只是检查库源码文件的有效性,只会做检查性的编译,而不会输出任何结果文件。注意,go get命令会把当前的代码包下载到$GOPATH中的第一个工作区的src录中,并安装。go build编译命令源码文件,则会在该命令的执行目录中生成一个可执行文件,上面的例子也印证了这个过程。
Go+ 调用其他进程方法教程(4.25)
liuzhen007的专栏
12-05 632
Go+ 概述 Go+ 是一门融合工程开发的 Go、数据科学领域的 Python、编程教学领域的 Scratch,以 Python 之形结合 Go 之心,让工程师处理数据不需要学习新的开发语言,让初学者学习编程、开发作品的门槛更低的编程语言。 正文 有时候,开发程序需要调用非 gop 的进程,Go+ 提供了调用其他进程的方法,今天我们就来了解一下这方面的一些内容。 导入包 Go+ 在调用其他进程时,一般会使用的工具库是 os/exec包,导入方式如下: import ( "os/e...
golang 调用外部程序
HG_Panda的博客
04-04 5966
Go提供的os/exec包可以执行外部程序 ubuntu 16.04 go 1.9版本 package main import( "os/exec" "fmt" ) func main(){ cmd := exec.Command("/bin/bash", "-c", "./tool 1.jpg") buf, err := cmd.Output() ...
1.22——第一个Go语言程序
qq_37899792的博客
03-13 423
gopath目录 gopath目录就是我们存储我们所编写源代码的目录。该目录下往往要有3个子目录:src,bin,pkg。 src ---- 里面每一个子目录,就是一个包。包内是Go的源码文件 pkg ---- 编译后生成的,包的目标文件 bin ---- 生成的可执行文件。 在控制台输出“Hello World!”非常简单,仅需要几行代码就可以搞定,如下所示:package(创建包) package main // 声明 main 包 import ( "fmt" // 导入 fmt 包,
go语言 windows下打包程序到linux中运行(winodws下打包linux可执行程序
12-22
1.先在main.go下打包成.exe可执行程序,测试确保代码可正确执行 //cd到main.go目录 go build //打包命令 如果打包成功则表示代码没问题,现在准备打包成linux下的程序 2.必须使用windows的cmd,不能使用powershell...
持续构建:Alpine镜像下构建的Go语言应用的运行问题
01-08
Go语言虽然是平台无关性的语言,但是构建出来的应用由于是可执行文件,所以注定无法像Java那样“一次编译、处处运行”,因为Java应用程序的二进制字节码下的解释由JVM这一层来实现,所以能够实现一次编译之后随处...
Ubuntu安装Go语言运行环境
01-20
在ubuntu论坛上看到一个抓取网页里的图片数据的帖子,于是就想着用GO语言来试下。那么先安装一个运行环境吧。以下安装方式在32位和64位的ubuntu12.04上都安装成功,并且所有命令,程序我都实际运行过,如果有问题,...
Go语言学习笔记.pdf 共174页
04-11
Go语言学习笔记.pdf 共174页是一本关于Go语言的详细学习笔记,涵盖了Go语言的基础知识、函数、数组、Maps、Structs、接口、并发、程序结构、标准库等方面的内容。本笔记共分为三大部分:Go语言基础、标准库和扩展库...
运行go语言代码的几种方法
jixn的博客
11-01 1738
go语言运行
Go语言的实时GC原理和实践
hyman.lu
05-13 5109
本文主要讲解Go语言的GC原理,理解这与实现低延迟之间的关系,以及为何这又会导致吞吐量的下降。
Go语言构建微服务(1)简介和基本原理
小5哥的专栏
05-31 3334
作者简介:在这个博客系列中,将使用Go编程语言构建微服务,并逐步添加必要的集成,以使它们在Spring Cloud / Netflix OSS环境中,很好地运行在Docker swam上。如果您不确定微服务是什么,建议您阅读Martin Fowler关于这方面的文章。有关微服务的操作模型的更多信息,我的同事Magnus的这篇博文很好地解释了关键概念。这个博客系列不会成为Go编程的初学者指南,尽管在...
Golang原理之goroutine与channel
uxff的专栏
12-06 5633
常见并发编程模型分类并发编程模型,顾名思义就是为了解决高并发充分利用多核特性减少CPU等待提高吞吐量而提出的相关的编程范式。目前为止,我觉得比较常见的并发编程模型大致可以分为两类: 基于消息(事件)的活动对象 基于CSP模型的协程的实现 其中基于消息(事件)的活动对象的并发模型,最典型的代表就是Akka的actor。actor的并发模型是把一个个计算序列按抽象为一个一个Actor对象,每一个Acto
使用Go语言工作400天后的感受
热门推荐
Sunface撩技术
03-28 7万+
我在2011年就听说了Go并学习了一段时间,坦白的说,那时候对Go是比较无感的,因为并没有看到Go的特别亮眼的地方,可能和我使用C、Erlang、Java有关,这三种语言可以写高性能、高并发、高可用的服务;包含了面相过程、面向并发、面向对象的思想,我觉得我并不需要再学习Go,何况那个时候好像也没宣传的那么优秀。 一切都发生在418天前,因为工作的需要,我开始写Go了,本来预期是一段压抑、蛋疼的旅程
go语言 命令行高效编译运行Go程序
m0_38068812的博客
06-30 5900
一.概述    上一篇文章中详细讲解了一个go语言编写简单的HelloWorld程序,那么我们该如何快速编译和运行呢二.运行helloworld程序    1.编写程序如下图所示:                   2.Ctrl + S 保存后,代码会自动格式化,如果代码没有自动格式化,说明很可能代码逻辑或者格式错误      3.CTR+R编译运行,在同一个目录下仅仅只能运行一个go文件,此处...
java和go的运行机制是什么
08-21
Java 和 Go 都是编译型语言,也就是说,它们的源代码会被编译成机器语言(二进制代码),然后在计算机上运行。 Java 的编译过程是将 Java 源代码编译成 Java 字节码,然后由 Java 虚拟机(JVM)来执行这些字节码。 JVM 是一种软件,它能够在计算机上运行 Java 字节码。 这意味着,只要在计算机上安装了 JVM,就可以在该计算机上运行任何用 Java 编写的程序。 Go 的编译过程是将 Go 源代码编译成机器语言,直接在计算机上运行。 Go 编译器会根据目标计算机的架构(例如 x86、x64 等)来生成对应的机器语言代码。 这意味着,Go 程序可以在任何支持 Go 的计算机上运行,而无需任何额外的运行时环境。 总的来说,Java 和 Go 的运行机制有一些相似之处,但也有一些不同之处。 Java 程序依赖于 JVM 的支持,而 Go 程序可以直接在计算机上运行

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
写文章

热门文章

  • 三种node服务启动和调试方式 36147
  • VSCode中JS脚本的运行(控制台输出配置) 28575
  • Vue使用axios发送post请求,后端无法接收怎么处理?(Djnago后台) 25951
  • 使用js实现5种加密解密算法(凯撒密码、字母倒排序、单表置换、维基利亚、转换加密算法) 21891
  • 前端(以Vue为例)webpack打包后dist文件包如何部署到django后台中 21769

分类专栏

  • Golang_深度认知 1篇
  • Python进阶__多维认知 19篇
  • Js/Ts__开发之道 11篇
  • Node&大前端技术__入木三分 11篇
  • 全栈__架构探索 5篇
  • MySQL及DB原理__开发必备 1篇
  • LInux&服务部署_最后N公里 3篇
  • 算法应用&深度学习__极客王道 24篇
  • 编程杂谈__搬砖经验 7篇

最新评论

  • TensorFlow报错Fetch argument None has invalid type class 'NoneType'

    weixin_59008262: 我的也是这样,请问最后怎么解决的?

  • 无头浏览器与Puppeteer中PDF生成应用指南

    qq_37911403: 目标网站图片质量较大,造成生成的pdf文件过大,该怎么解决呢

  • npm安装过程中core-js始终无法安装成功报错问题

    RedSparks: 救命了,感谢大佬,试了好多方法了

  • TensorFlow报错Fetch argument None has invalid type class 'NoneType'

    m0_50019809: 你好,我报错的源码是这样的 [code=python] def train(self, dataset): log_out('****EPOCH {}****'.format(self.training_epoch), self.Log_file) self.sess.run(dataset.train_init_op) while self.training_epoch < self.config.max_epoch: t_start = time.time() try: ops = [self.train_op, self.extra_update_ops, self.merged, self.loss, self.logits, self.labels, self.accuracy] # _, _, summary, l_out, probs, labels, acc = self.sess.run(ops, {self.is_training: True}) _, _, summary, l_out, probs, labels, acc = self.sess.run(ops, {self.is_training: True}) self.train_writer.add_summary(summary, self.training_step) t_end = time.time() [/code] 和这里最后一种情况报错一样,但是这里的train_op已经是_,但是还是有问题,应该怎么办

  • Vue相关的学习视频分享

    奔跑的小G: 失效了,可以再发一次吗

大家在看

  • 一篇文章看懂Redission原理 2752
  • 数据结构预备知识(Java):包装类&泛型 1659

最新文章

  • 经典SQL题目-求第N高的薪水的解法汇总及知识点复习
  • vscode在自动填充后失去常规代码提示的问题(连续自动补充失效)
  • 随身Token动态令牌工作原理的思考
2021年7篇
2020年7篇
2019年6篇
2018年59篇

目录

目录

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AlexGeek

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

深圳SEO优化公司遵义网站优化公司长治网站改版哪家好泰州网站定制价格兰州营销型网站建设公司陇南建站推荐乌海外贸网站建设哪家好吉林网站定制辽阳网站优化按天收费多少钱通化网站优化公司临沂网站制作泰州设计网站推荐贵港营销型网站建设哪家好揭阳百度爱采购价格达州模板网站建设报价盐田优秀网站设计价格赤峰网站优化排名塘坑外贸网站建设多少钱福永网站定制焦作阿里店铺运营价格醴陵企业网站制作推荐爱联网站制作报价清远网站设计模板公司滨州网站优化按天收费镇江seo排名推荐湖州模板推广多少钱双龙网站设计模板报价商洛网络营销哪家好通化英文网站建设罗湖网站优化推广报价喀什至尊标王公司歼20紧急升空逼退外机英媒称团队夜以继日筹划王妃复出草木蔓发 春山在望成都发生巨响 当地回应60岁老人炒菠菜未焯水致肾病恶化男子涉嫌走私被判11年却一天牢没坐劳斯莱斯右转逼停直行车网传落水者说“没让你救”系谣言广东通报13岁男孩性侵女童不予立案贵州小伙回应在美国卖三蹦子火了淀粉肠小王子日销售额涨超10倍有个姐真把千机伞做出来了近3万元金手镯仅含足金十克呼北高速交通事故已致14人死亡杨洋拄拐现身医院国产伟哥去年销售近13亿男子给前妻转账 现任妻子起诉要回新基金只募集到26元还是员工自购男孩疑遭霸凌 家长讨说法被踢出群充个话费竟沦为间接洗钱工具新的一天从800个哈欠开始单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#中国投资客涌入日本东京买房两大学生合买彩票中奖一人不认账新加坡主帅:唯一目标击败中国队月嫂回应掌掴婴儿是在赶虫子19岁小伙救下5人后溺亡 多方发声清明节放假3天调休1天张家界的山上“长”满了韩国人?开封王婆为何火了主播靠辱骂母亲走红被批捕封号代拍被何赛飞拿着魔杖追着打阿根廷将发行1万与2万面值的纸币库克现身上海为江西彩礼“减负”的“试婚人”因自嘲式简历走红的教授更新简介殡仪馆花卉高于市场价3倍还重复用网友称在豆瓣酱里吃出老鼠头315晚会后胖东来又人满为患了网友建议重庆地铁不准乘客携带菜筐特朗普谈“凯特王妃P图照”罗斯否认插足凯特王妃婚姻青海通报栏杆断裂小学生跌落住进ICU恒大被罚41.75亿到底怎么缴湖南一县政协主席疑涉刑案被控制茶百道就改标签日期致歉王树国3次鞠躬告别西交大师生张立群任西安交通大学校长杨倩无缘巴黎奥运

深圳SEO优化公司 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化