Go Concurrency Patterns: Context

Go Concurrency Patterns: Context

原文地址:https://blog.golang.org/context

Introduction

在 Go 语言实现的服务器上,我们总是使用 goroutine 来处理与客户端建立的连接, 给每个连接分配一个独立的 goroutine. 在请求的 handler 中也通常会再开启额外的 goroutine 来访问后台的各种服务,比如 数据库操作,访问 RFC 服务。 在这种情况下,一个客户端连接往往会对应一组 goroutine,这些 goroutine 在运行过程中往往还需要用到请求相关的数据,比如,用户id,授权token,请求的 deadline 等. 当一个请求超时,或者被cancel之后,这些 goroutine 就需要被尽快的释放出来,以便为其他请求服务.

为此 Google 开发了一个 context 包,使用这个包使得在一组 共routine 中传递请求相关的参数,cancel 信号,deadline 变得非常容易. 本文将描述如何使用 context 包,并且提供一个完整的例子来描述如何使用它。

Context

Context 包的核心是 Context 类型,它的定义如下:

// A Context carries a deadline, cancelation signal, and request-scoped values
// across API boundaries. Its methods are safe for simultaneous use by multiple
// goroutines.
type Context interface {// Done returns a channel that is closed when this Context is canceled // or times out.Done() <-chan struct{}// Err indicates why this context was canceled, after the Done channel// is closed.Err() error// Deadline returns the time when this Context will be canceled, if any.Deadline() (deadline time.Time, ok bool)// Value returns the value associated with key or nil if none.Value(key interface{}) interface{}
}

Done

Deadline

Value 允许 context 携带请求相关的数据,必须确保在多个 goroutine 中访问中访问这些数据是安全的.

Derived contexts

Context 包提供了从一个现有的 context 派生出其他 context 的方法. 这些 context 共同构成一颗 context 树: 当一个 context 被 cancel 的时候,从这个树派生出来的 context 也会被 cancel

Background 是任何 context 树的根, 它不能被 cancel:

// Background returns an empty Context. It is never canceled, has no deadline,
// and has no values. Background is typically used in main, init, and tests,
// and as the top-level Context for incoming requests.
func Background() Context

WithCancelWithTimeout 用于从当前 context 派生新的 context. 与客户端请求相关的 context 通常会被 cancel 当处理请求的 handler 退出的时候. WithCancel 对于处理多余的请求非常有用. WithTimeout 通常用来给一个请求设置一个超时时间.

// WithCancel returns a copy of parent whose Done channel is closed as soon as
// parent.Done is closed or cancel is called.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc)// A CancelFunc cancels a Context.
type CancelFunc func()// WithTimeout returns a copy of parent whose Done channel is closed as soon as
// parent.Done is closed, cancel is called, or timeout elapses. The new
// Context's Deadline is the sooner of now+timeout and the parent's deadline, if
// any. If the timer is still running, the cancel function releases its
// resources.
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)

WithValue 提供了一种关联请求数据和 context 的方法:

// WithValue returns a copy of parent whose Value method returns val for key.
func WithValue(parent Context, key interface{}, val interface{}) Context

Example: Google Web Search

我们的示例是一个用户处理像 /search?q=golang&timeout=1s 这种请求的 HTTP 服务器.
HTTP 服务器将查询 “golang” 的请求转发到 Google Web Search API, 并返回请求结果. timeout 参数告诉服务器在多久之后 cancel 这个请求.

代码被组织在三个 package 中:

  • server: 提供 main 方法和对应与 /search 的 hander
  • userip: 提供了从用户请求解析用户 IP 地址和 将 IP 地址与 context 绑定的方法
  • google: 提供了 Search 方法的实现
server
func handleSearch(w http.ResponseWriter, req *http.Request) {// ctx is the Context for this handler. Calling cancel closes the// ctx.Done channel, which is the cancellation signal for requests// started by this handler.var (ctx    context.Contextcancel context.CancelFunc)timeout, err := time.ParseDuration(req.FormValue("timeout"))if err == nil {// The request has a timeout, so create a context that is// canceled automatically when the timeout expires.ctx, cancel = context.WithTimeout(context.Background(), timeout)} else {ctx, cancel = context.WithCancel(context.Background())}defer cancel() // Cancel ctx as soon as handleSearch returns.// Check the search query.query := req.FormValue("q")if query == "" {http.Error(w, "no query", http.StatusBadRequest)return}// Store the user IP in ctx for use by code in other packages.userIP, err := userip.FromRequest(req)if err != nil {http.Error(w, err.Error(), http.StatusBadRequest)return}ctx = userip.NewContext(ctx, userIP)// Run the Google search and print the results.start := time.Now()results, err := google.Search(ctx, query)elapsed := time.Since(start)if err := resultsTemplate.Execute(w, struct {Results          google.ResultsTimeout, Elapsed time.Duration}{Results: results,Timeout: timeout,Elapsed: elapsed,}); err != nil {log.Print(err)return}
userip
// The key type is unexported to prevent collisions with context keys defined in
// other packages.
type key int// userIPkey is the context key for the user IP address.  Its value of zero is
// arbitrary.  If this package defined other context keys, they would have
// different integer values.
const userIPKey key = 0// FromRequest 解析用户 IP 地址
func FromRequest(req *http.Request) (net.IP, error) {ip, _, err := net.SplitHostPort(req.RemoteAddr)if err != nil {return nil, fmt.Errorf("userip: %q is not IP:port", req.RemoteAddr)}return ip, nil
}// NewContext 返回一个携带有用户 IP 地址的 context 实例
func NewContext(ctx context.Context, userIP net.IP) context.Context {return context.WithValue(ctx, userIPKey, userIP)
}func FromContext(ctx context.Context) (net.IP, bool) {// ctx.Value returns nil if ctx has no value for the key;// the net.IP type assertion returns ok=false for nil.userIP, ok := ctx.Value(userIPKey).(net.IP)return userIP, ok
}
google
func Search(ctx context.Context, query string) (Results, error) {// Prepare the Google Search API request.req, err := http.NewRequest("GET", "https://ajax.googleapis.com/ajax/services/search/web?v=1.0", nil)if err != nil {return nil, err}q := req.URL.Query()q.Set("q", query)// If ctx is carrying the user IP address, forward it to the server.// Google APIs use the user IP to distinguish server-initiated requests// from end-user requests.if userIP, ok := userip.FromContext(ctx); ok {q.Set("userip", userIP.String())}req.URL.RawQuery = q.Encode()var results Resultserr = httpDo(ctx, req, func(resp *http.Response, err error) error {if err != nil {return err}defer resp.Body.Close()// Parse the JSON search result.// https://developers.google.com/web-search/docs/#fonjevar data struct {ResponseData struct {Results []struct {TitleNoFormatting stringURL               string}}}if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {return err}for _, res := range data.ResponseData.Results {results = append(results, Result{Title: res.TitleNoFormatting, URL: res.URL})}return nil})// httpDo waits for the closure we provided to return, so it's safe to// read results here.return results, err
}func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error {// Run the HTTP request in a goroutine and pass the response to f.c := make(chan error, 1)req = req.WithContext(ctx)go func() { c <- f(http.DefaultClient.Do(req)) }()select {case <-ctx.Done():<-c // Wait for f to return.return ctx.Err()case err := <-c:return err}
}

Adapting code for Contexts

许多服务器框架都会提供 package 和 types 来携带请求数据. 在这种情况下,我们可以通过实现 Context 接口来将已有框架的类型转化为 context 类型.

举个例子, Gorilla package 通过提供了保存请求参数的 map 来携带请求数据. 在 Gorilla 中,我们可以提供一个实现 context 接口的自定义 context 类型,该类型重写 Value 方法,该方法返回 Gorilla 中保存的 map.

Conclusion

在谷歌中,我们要求工程师总是将 context 参数作为传入和传出请求之间调用路径上的每个函数的第一个参数类传递. 这使得许多不同团队开发的 Go 代码具有很好的互操作性。它提供了对超时和 cancel 的简单控制,并确保数据正确传输。

希望基于 context 的服务器框架应该提供自己的 context 实现,以便框架自身的 context 与标准 go context 的兼容. 因此,它们的客户端代码通常都接受一个来自调用者的 context 对象.

Related articles

  • Go Concurrency Patterns: Pipelines and cancellation
  • Introducing the Go Race Detector
  • Go Concurrency Patterns: Timing out, moving on
  • Share Memory By Communicating

END!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/499674.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go Concurrency Patterns: Timing out, moving on

原文地址&#xff1a;https://blog.golang.org/concurrency-timeouts 并发变成有它自己的风格. 一个非常好的例子就是 timeout. 虽然 go 的 channel 没有直接支持 timeout 机制&#xff0c;但是要实现它非常容易. 比如说&#xff0c;我们想从一个 channel ch 中接收数据&#…

Go Concurrency Patterns: Pipelines and cancellation

原文地址&#xff1a; https://blog.golang.org/pipelines 简介 Go 语言提供的并发原语使得可以很方便的构建数据流 pipeline&#xff0c;使用这样的 pipeline 可以高效的利用 I/O 和多 cpu 的优势. 这篇文章我们将展示如何构建并使用 pipeline. 什么是 pipeline ? 在 go 语…

QTcpSocket connectToHost 建立连接速度慢问题

问题场景 在使用 QT 开发一个客户端 App 的时候&#xff0c;我们通过 QTcpSocket 与后台服务器进程通信。 后台程序使用其他语言编写。 问题&#xff1a; 在客户端启用之后尝试建立与后台程序的 TCP 连接的时候&#xff0c;发现连接速度非常慢&#xff08;肉眼可见的慢&#x…

WinSock I/O 模型 -- Select 模型

简介 Select 模型是 WinSock 中最常见的 I/O 模型&#xff0c;这篇文章我们就来看看如何使用 Select api 来实现一个简单的 TCP 服务器. API 基础 Select 模型依赖 WinSock API Select 来检查当前 Socket 是否可写或者可读。 使用这个 API 的优点是我们不需要使用阻塞的 So…

WinSock I/O 模型 -- WSAEventSelect 模型

简介 WSAEventSelect 模型也是 WinSock 中最常见的异步 I/O 模型。 这篇文章我们就来看看如何使用 WSAEventSelect api 来实现一个简单的 TCP 服务器. API 基础 WSAEventSelect WSAEventSelect 用来把一个 SOCKET 对象和一个 WSAEVENT 对象关联起来。 lNetworkEvents 表示…

WinSock I/O 模型 -- WSAAsyncSelect 模型

简介 WSAAsyncSelect 模型也是 WinSock 中常见的异步 I/O 模型。 使用这个模型&#xff0c;网络应用程序通过接收以 Windows 消息为基础的网络事件通知来处理网络请求。 这篇文章我们就来看看如何使用 WSAAsyncSelect api 来实现一个简单的 TCP 服务器. API 基础 要使用 W…

WinSock I/O 模型 -- OVERLAPPED I/O 模型

简介 OVERLAPPED I/O 模型也是 WinSock 中常见的异步 I/O 模型&#xff0c;相比于我们之前提到的 Select 模型&#xff0c;WSAAsyncSelect 模型 和 WSAEventSelect 模型有更好的性能. 为了方便描述&#xff0c;下文我们将称 Overlapped I/O 模型为 “重叠模型”. 重叠模型的…

WinSock I/O 模型 -- IOCP 模型

前言 IOCP 全称 Input/Ouput Completion Ports&#xff0c;中文中翻译一般为“完成端口”&#xff0c;本文中我们使用 IOCP 简写. IOCP 模型是迄今为止最为复杂的一种 I/O 模型&#xff0c;但是同时通过使用 IOCP 我们往往可以达到最佳的系统性能. 当你的网络应用程序需要管理…

GTank iOS App Technical Support

GTank iOS App Technical Support For All Email: z253951598outlook.com TEL: 86-17782749061 App Screen Shoots ​​

证书体系: CSR 解析

原文同时发布于本人个人博客&#xff1a; https//kutank.com/blog/cert-csr/ 简介 CSR 全称 “证书签名请求”(Certificate Signing Request). 本文我们将来详细的学习 CSR 的知识&#xff0c;重点集中在 CSR 所包含的信息&#xff0c;及其意义。 CSR 的作用: CSR 通常由想要获…

胡思乱想

学了一段时间的OGRE,才知道什么才称得上"建筑师",而我们只不过是"砌墙匠" 他们在算法算法,我们在Coding Coding,怎样才能有所改观呢~~~想当初还不如选数学专业再来学计算机可能好些, 但是既然选择了先学计算机这条路,那就先Coding,边Coding边提高数学能力…

关于数据库备份的问题

首先我们来看数据库帮助上面的sql语句&#xff1a; BACKUP DATABASE Northwind TO DISK c:/Northwind.bakRESTORE FILELISTONLY FROM DISK c:/Northwind.bakRESTORE DATABASE TestDB FROM DISK c:/Northwind.bak WITH MOVE Northwind TO c:/test/testdb.mdf, MOVE N…

关于函数指针调用C++非静态成员

当在类里面定义函数指针&#xff0c;而函数指针会指向类里面的成员的时候&#xff0c;这个时候成员需要定义为静态成员。实例代码如下&#xff1a; //.h#define SCMD_REGISTER 0class CCallFuctionList{public:CCallFuctionList();virtual ~CCallFuctionList(void);typedef…

重构心得

重构入手&#xff1a; 1. 找到牵连最广模块。 2. 找到上述模块中需要重构的相关的子类。 3. 原来代码不删除&#xff0c;保证编译运行。 4. 陆续重构其他模块 再列出我觉得可以借鉴的重构方法。【摘自代码大全】 1.保存初始代码。用你的版本控制系统保存一个初始版本&#x…

跨模块中的分配内存问题

现在有dll模块A,dll模块B以及静态库C, C中有个全局Create()函数负责创建对象M,当第二次调用Create()的时候会增加引用计数&#xff0c;并返回原来创建的对象M。Relase()会减少引用计数。当引用计数为0时&#xff0c;销毁对象M。现在在模块A中创建的初始化对象M&#xff0c;模块…

CListControl的OnMouseMove和OnNcHitTest

实际案例如下&#xff1a; 将CListCtrl做成菜单样式。需要处理当鼠标移到ClistCtrl上的事件。 处理逻辑这样&#xff1a;当鼠标移动到CListCtrl区域时候&#xff0c;将CListCtrl上所有ITem置为非选中状态&#xff0c;然后调用HitTest得到行数再将所选行置为选中状态。当鼠标移…

关于函数指针续

前面有提到过关于函数指针调用C非静态成员&#xff0c;解决了在类内调用函数指针的问题。 class CCallFuctionList { public: CCallFuctionList(); virtual ~CCallFuctionList(void); typedef void (CCallFuctionList::*FUNCPTR)(); typedef std::multimap<unsi…

关于函数指针续二

前篇文章解决了关于函数指针的不同类成员函数传递的问题。不知道细心的朋友看见我上篇文章后&#xff0c;是否发现参数必须被限制为特定的参数。 原来改进的代码如下&#xff1a; class CCallFuctionList { public:CCallFuctionList(); virtual ~CCallFuctionL…

HGE2D引擎按键消息分析

我们自己先动手来模仿HGE其键盘特殊按键消息响应&#xff0c;其中所涉及到的数据操作含义稍后再介绍。 首先创建基于对话框CGetKeyBoardTestDlg的程序&#xff0c;创建一个STATIC控件ID为IDC_STATIC_CONTENT在对话框上面。添加成员 unsigned char kbstate[256]; 和int flag; 在…

HGE引擎适用于MFC的修改

打开hge181/src/core/system.cpp 找到System_Initiate()函数&#xff0c;可以看见里面有段代码是用于创建窗口。 // Register window classwinclass.style CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW;winclass.lpfnWndProc WindowProc;winclass.cbClsExtra 0;wincl…