go语言-context的基本使用

1. 什么是 Context?

Go 1.7 标准库引入 context,中文译作“上下文”,准确说它是 goroutine 的上下文,包含 goroutine 的运行状态、环境、现场等信息。

context 主要用来在 goroutine 之间传递上下文信息,包括:取消信号、超时时间、截止时间、k-v 等。

Context,也叫上下文,它的接口定义如下

type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() <-chan struct{}
    Err() error
    Value(key interface{}) interface{}
}

可以看到 Context 接口共有 4 个方法

  • Deadline:返回的第一个值是截止时间,到了这个时间点,Context 会自动触发 Cancel 动作。返回的第二个值是 一个布尔值,true 表示设置了截止时间,false 表示没有设置截止时间,如果没有设置截止时间,就要手动调用 cancel 函数取消 Context。

  • Done:返回一个只读的通道(只有在被cancel后才会返回),类型为 struct{}。当这个通道可读时,意味着parent context已经发起了取消请求,根据这个信号,开发者就可以做一些清理动作,退出goroutine。

  • Err:返回 context 被 cancel 的原因。

  • Value:返回被绑定到 Context 的值,是一个键值对,所以要通过一个Key才可以获取对应的值,这个值一般是线程安全的。

2. 为什么要用Context

  • 用于控制goroutine的结束,但它解决的并不是 能不能 的问题,而是解决 更好用 的问题。

2.1. 当不用Context时,利用channel+select来主动让goroutine停止

示例1


package main

import (
	"fmt"
	"time"
)

/*
1. 利用channel控制goroutine的停止
*/

func main() {

	stopChan := make(chan bool)

	go func() {
		for {
			select {
			case <-stopChan:
				fmt.Println("goroutin1 exit.")
				return
			default:
				fmt.Println("goroutin1 sleep 1s, keep going.")
				time.Sleep(time.Second * 2)
			}
		}
	}()

	go func() {
		for {
			select {
			case <-stopChan:
				fmt.Println("goroutin2 exit.")
				return
			default:
				fmt.Println("goroutin2 sleep 1s, keep going.")
				time.Sleep(time.Second * 3)
			}
		}
	}()

	time.Sleep(10 * time.Second)
	fmt.Println("10s 时间到了,主进程需要退出了.")
	// 发送信号让goroute1结束
	stopChan <- true

	// 发送信号让goroute2结束
	stopChan <- true
	time.Sleep(5 * time.Second)
}



示例2

package main

import (
	"fmt"
	"time"
)

/*

1. 利用关闭channel的方法,让2个goroutine同时结束
*/

func main() {

	stopChan := make(chan bool)

	go func() {
		for {
			select {
			case <-stopChan:
				fmt.Println("goroutin1 exit.")
				return
			default:
				fmt.Println("goroutin1 sleep 1s, keep going.")
				time.Sleep(time.Second * 2)
			}
		}
	}()

	go func() {
		for {
			select {
			case <-stopChan:
				fmt.Println("goroutin2 exit.")
				return
			default:
				fmt.Println("goroutin2 sleep 1s, keep going.")
				time.Sleep(time.Second * 3)
			}
		}
	}()

	time.Sleep(10 * time.Second)
	fmt.Println("10s 时间到了,主进程需要退出了.")
	// 利用关闭channel的方法,让2个goroutine同时结束
	close(stopChan)

	time.Sleep(5 * time.Second)
}


2.2 使用context来主动让goroutine停止

先ctx, cancel := context.WithCancel(context.Background()) 创建一个ctx实例
再利用cancel()函数执行控制goroutine的停止

package main

import (
	"context"
	"fmt"
	"time"
)

/*
// 利用context,手动让2个goroutine同时结束[是不是更简单?]
*/
func main() {

	ctx, cancel := context.WithCancel(context.Background())

	go func() {
		for {
			select {
			case <-ctx.Done():
				fmt.Println("goroutin1 exit.")
				return
			default:
				fmt.Println("goroutin1 sleep 1s, keep going.")
				time.Sleep(time.Second * 1)
			}
		}
	}()

	go func() {
		for {
			select {
			case <-ctx.Done():
				fmt.Println("goroutin2 exit.")
				return
			default:
				fmt.Println("goroutin2 sleep 1s, keep going.")
				time.Sleep(time.Second * 1)
			}
		}
	}()
	time.Sleep(10 * time.Second)
	fmt.Println("10s 时间到了,goroutine需要退出了.")
	
	// 利用context的方法,手动让2个goroutine同时结束
	cancel()

	time.Sleep(5 * time.Second)
}


2.3 使用context实现goroutine的超时控制

  • 使用场景:让goroutine执行一个任务,如果在指定时间内没有完成,这利用context的WithTimeout()主动让goroutine退出
package main

import (
    "fmt"
    "time"
    "context"
)

// 场景: 如果你需要对一个用协程启动的函数做超时控制,可以用context来完成goroutine的控制

func main()  {
    // 设置一个用于超时控制的context ctx, ctx作为参数可以用来作为协程的超时控制
    ctx,cancel := context.WithTimeout(context.Background(),10 * time.Second)
    defer cancel()

    // ctx作为参数传递给需要做超时控制的函数

    go Monitor(ctx)

    time.Sleep(20 * time.Second)
}

func Monitor(ctx context.Context)  {
    for {

        select {
        // 如果context 超时,ctx.Done()就会返回一个空接口 struct{}
        case <- ctx.Done():
            // 如果超时时间到了,就退出循环
            fmt.Println(ctx.Err())
            return
        // 如果没有超时,打印输出后继续循环
        default:
            time.Sleep(1*time.Second)
            fmt.Println("monitor")
        }


    }
}

2.4 利用context向goroutine传递参数

  • 除了超时控制与主动停止goroutine,还有可以通过Context传递上下文变量给其他协程。这样可以避免在协程之间传递大量的变量,代码更整洁可维护。下面的例子通过WithValue传递给协程一个变量,并且通过channel在协程之间通信。
package main

import (
	"context"
	"fmt"
	"time"
)

func main() {

	// 为ctx设置一个key-value
	ctx := context.Background()
	ctx = context.WithValue(ctx, "hello", "world")
	x := ctx.Value("hello")
	fmt.Println("x=", x) // world

	// 将key-vluae值传递到goroutine
	go work(ctx)

	time.Sleep(3 * time.Second)

}
func work(ctx context.Context) {
	fmt.Println("do worker.")
	fmt.Println("hello=", ctx.Value("hello")) // world,利用context传递key-value
	// 继续传递到下层goroutine
	go subwork(ctx)
}

func subwork(ctx context.Context) {
	fmt.Println("do subwork.")
	fmt.Println("hello=", ctx.Value("hello")) // world,利用context传递key-value到更进一层
}

程序输出:

x= world
do worker.
world
do subwork.
world
  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Fabric 支持运行 Go 语言智能合约。要在 Fabric 中使用 Go 语言智能合约,你需要: 1. 在你的本地安装 Go 编程语言环境。 2. 下载和安装 Fabric 的 Go SDK。 3. 使用 Go 语言编写智能合约,并将其编译为可执行文件。 4. 将编译后的合约文件打包为 Chaincode 包,并将其部署到 Fabric 网络中。 5. 在你的应用程序中调用智能合约。 下面简要介绍一下如何使用 Go 语言编写、编译和部署智能合约: 1. 编写智能合约 使用 Go 语言编写智能合约与编写其他 Go 程序并没有太大区别。你需要实现智能合约接口,包括 Init 和 Invoke 方法。Init 方法在部署合约时执行,而 Invoke 方法在调用合约时执行。 例如,以下是一个简单的示例智能合约,它实现了 Init 和 Invoke 方法,用于存储和检索键值对: ```go package main import ( "fmt" "github.com/hyperledger/fabric/core/chaincode/shim" pb "github.com/hyperledger/fabric/protos/peer" ) type SimpleChaincode struct { } func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { return shim.Success(nil) } func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { function, args := stub.GetFunctionAndParameters() if function == "put" { key := args[0] value := args[1] err := stub.PutState(key, []byte(value)) if err != nil { return shim.Error(err.Error()) } return shim.Success(nil) } else if function == "get" { key := args[0] value, err := stub.GetState(key) if err != nil { return shim.Error(err.Error()) } return shim.Success(value) } return shim.Error("Invalid function name") } func main() { err := shim.Start(new(SimpleChaincode)) if err != nil { fmt.Printf("Error starting chaincode: %s", err) } } ``` 2. 编译智能合约 使用 Go 语言编写的智能合约需要编译为可执行文件。你可以使用 Go 语言提供的工具来编译合约,如 go build 命令。 例如,假设你的合约代码保存在 $GOPATH/src/mychaincode 目录下,你可以使用以下命令编译智能合约: ``` go build -o mychaincode ``` 这将生成一个名为 mychaincode 的可执行文件。 3. 打包智能合约 编译后的智能合约需要打包为 Chaincode 包,然后才能部署到 Fabric 网络中。Chaincode 包是一个 tar 归档文件,其中包含智能合约的二进制文件和一些元数据。 你可以使用 Fabric 提供的 `peer` 命令行工具来打包合约。例如,假设你的智能合约代码保存在 $GOPATH/src/mychaincode 目录下,你可以使用以下命令打包合约: ``` peer chaincode package -n mychaincode -v 1.0 -p mychaincode -s -S -i "OR ('Org1MSP.peer', 'Org2MSP.peer')" mychaincode.tar.gz ``` 这将生成一个名为 mychaincode.tar.gz 的 Chaincode 包。 4. 部署智能合约 部署智能合约需要将 Chaincode 包安装到 Fabric 网络中。你可以使用 Fabric 提供的 `peer` 命令行工具来安装和实例化合约。 例如,假设你已经在本地启动了 Fabric 网络,并且有一个名为 mychannel 的通道可用,你可以使用以下命令安装和实例化合约: ``` peer chaincode install mychaincode.tar.gz peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n mychaincode -v 1.0 -c '{"Args":[]}' -P "OR ('Org1MSP.peer', 'Org2MSP.peer')" ``` 5. 调用智能合约 部署智能合约后,你可以在你的应用程序中调用它。你需要使用 Fabric 提供的 SDK 来与合约进行交互。 例如,以下是一个简单的 Go 语言应用程序,它使用 Fabric Go SDK 来调用智能合约: ```go package main import ( "fmt" "github.com/hyperledger/fabric-sdk-go/pkg/client/channel" "github.com/hyperledger/fabric-sdk-go/pkg/fabsdk" ) func main() { // 创建 Fabric SDK 实例 sdk, err := fabsdk.New(nil) if err != nil { panic(err) } defer sdk.Close() // 创建 Fabric Channel 客户端 clientChannelContext := sdk.ChannelContext("mychannel", fabsdk.WithUser("User1")) channelClient, err := channel.New(clientChannelContext) if err != nil { panic(err) } // 调用智能合约 response, err := channelClient.Execute(channel.Request{ ChaincodeID: "mychaincode", Fcn: "put", Args: [][]byte{[]byte("key"), []byte("value")}, }) if err != nil { panic(err) } fmt.Printf("Response: %s\n", response.Payload) } ``` 以上就是使用 Go 语言编写、编译和部署智能合约的基本步骤。需要注意的是,这只是一个简单的示例,实际生产环境中需要考虑更多的安全和性能问题。

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值