主頁 > 區塊鏈 > Golang 并發賦值的安全性探討

Golang 并發賦值的安全性探討

2021-04-05 10:46:10 區塊鏈

在這里插入圖片描述

文章目錄

  • 1.什么是并發安全
  • 2.struct 并發賦值安全嗎
  • 3.如何保證并發賦值的安全性
  • 4.哪些型別并發賦值是安全的
    • 4.1 基本型別的并發賦值
      • 4.1.1 位元組型、布爾型、整型、浮點型、字符型(安全)
      • 4.1.2 復數型(不安全)
      • 4.1.3 字串(不安全)
    • 4.2 復合資料型別的并發賦值
      • 4.2.1 指標(安全)
      • 4.2.2 函式(安全)
      • 4.2.2 陣列、切片、字典、通道、介面(不安全)
        • 陣列
        • 切片
        • 字典
        • 通道
        • 介面
  • 5.小結
  • 參考文獻

我們知道 Golang 中變數的賦值不是并發安全的,實際情況果真如此嗎?

1.什么是并發安全

并發安全就是程式在并發情況下執行的結果是正確的,

比如對一個變數簡單的自增操作count++,在非并發下很好理解,而在并發情況下卻容易出現預期之外的結果,這樣的代碼就是非并發安全的,

因為count++其實是分成兩步執行的,當分成了兩步執行,那么其他協程就可以趁著這個時間間隙作怪,

如一下 a b 兩個協程同時 count++:

count:= 1
a > 讀取count : 1
b > 讀取count : 1
a > 計算count+1 : 2
b > 計算count+1 : 2
a > 賦值count : 2
b > 賦值count : 2

這就會發生明明 a b 協程計算了兩次,可結果還是 2,

2.struct 并發賦值安全嗎

對一個簡單變數的自增都會出現偏差,那么賦值一個更為復雜的結構體會不會有問題呢?

例如以下代碼,在多協程的情況下,并發使用兩個不同的值對結構體變數進行賦值,如果結構體成員出現例外情況, 那么說明并發出現了問題,

type Test struct {
	X int
	Y int
}

func main() {
	var g Test

	for i := 0; i < 1000000; i++ {
		var wg sync.WaitGroup
		// 協程 1
		wg.Add(1)
		go func() {
			defer wg.Done()
			g = Test{1,2}
		}()

		// 協程 2
		wg.Add(1)
		go func(){
			defer wg.Done()
			g = Test{3,4}
		}()
		wg.Wait()

		// 賦值例外判斷
		if !((g.X == 1 && g.Y == 2) || (g.X == 3 && g.Y == 4)) {
			fmt.Printf("concurrent assignment error, i=%v g=%+v", i, g)
			break
		}
	}
}

運行一次或多次,將出現賦值例外,

concurrent assignment error, i=48714 g={X:1 Y:4}

結構體中有多個欄位,協程 1 賦值了欄位 X,協程 2 賦值了欄位 Y,此時整個結構體既不是協程 1 想要的結果,也不是協程 2 想要的結果,可見 struct 賦值時,并不是原子操作,各個欄位的賦值是獨立的,在并發操作的情況下可能會出現例外,

3.如何保證并發賦值的安全性

Golang 早已想到該問題,并為我們提供一個開箱即用的型別 atomic.Value 來保證賦值的并發安全,

// A Value provides an atomic load and store of a consistently typed value.
// The zero value for a Value returns nil from Load.
// Once Store has been called, a Value must not be copied.
//
// A Value must not be copied after first use.
type Value struct {
	v interface{}
}

讓我們借助 atomic.Value 來完成對 struct 的安全并發賦值,

func main() {
	var v atomic.Value

	for i := 0; i < 1000000; i++ {
		var wg sync.WaitGroup
		// 協程 1
		wg.Add(1)
		go func() {
			defer wg.Done()
			v.Store(Test{1,2})
		}()

		// 協程 2
		wg.Add(1)
		go func(){
			defer wg.Done()
			v.Store(Test{3,4})
		}()
		wg.Wait()

		// 賦值例外判斷
		g := v.Load().(Test)
		if (g.X == 1 && g.Y == 2) || (g.X == 3 && g.Y == 4) {
		} else {
			fmt.Printf("concurrent assignment error, i=%v g=%+v", i, g)
			break
		}
	}
}

上面執行將不會出現并發賦值例外的情況,

4.哪些型別并發賦值是安全的

我們已經知道了 struct 因為存在多個欄位,賦值時各個欄位時獨立完成,所以并發不安全,那么對于 Golang 中其他的資料型別,并發賦值是安全的嗎?

Golang 中資料型別可以分類兩大類:基本資料型別和復合資料型別,

基本資料型別有:位元組型,布爾型、整型、浮點型、字符型、復數型、字串,

復合資料型別包括:指標、陣列、切片、結構體、字典、通道、函式、介面,

復合資料類又可細分為如下三類:
(1)非參考型別:陣列、結構體;
(2)參考型別:指標、切片、字典、通道、函式;
(3)介面,

下面一一列舉哪些資料型別是并發不安全的,

4.1 基本型別的并發賦值

4.1.1 位元組型、布爾型、整型、浮點型、字符型(安全)

由于位元組型、布爾型、整型、浮點型、字符型的位寬不會超過 64 位,在 64 位的指令集架構中可以由一潭訓器指令完成,不存在被細分為更小的操作單位,所以這些型別的并發賦值是安全的,

下面以浮點型為例進行測驗,

func main() {
	var g float64

	for i := 0; i < 1000000; i++ {
		var wg sync.WaitGroup
		// 協程 1
		wg.Add(1)
		go func() {
			defer wg.Done()
			g = 1.1
		}()

		// 協程 2
		wg.Add(1)
		go func(){
			defer wg.Done()
			g = 2.2
		}()
		wg.Wait()

		// 賦值例外判斷
		if g != 1.1) && g != 2.2 {
			fmt.Printf("concurrent assignment error, i=%v g=%+v", i, g)
			break
		}
	}
}

上面個的測驗代碼對一個 float64 型別的變數進行并發賦值是沒有問題的,其他型別讀者可自行驗證,

4.1.2 復數型(不安全)

按照上面的分析,因為復數型分為實部和虛部,兩者的賦值是分開進行的,所以復數型別并發賦值是不安全的,

func main() {
	var g complex64

	for i := 0; i < 1000000; i++ {
		var wg sync.WaitGroup
		// 協程 1
		wg.Add(1)
		go func() {
			defer wg.Done()
			g = complex(1,2)
		}()

		// 協程 2
		wg.Add(1)
		go func(){
			defer wg.Done()
			g = complex(3,4)
		}()
		wg.Wait()

		// 賦值例外判斷
		if g != complex(1,2) && g != complex(3,4) {
			fmt.Printf("concurrent assignment error, i=%v g=%+v", i, g)
			break
		}
	}
}

運行輸出:

concurrent assignment error, i=131512 g=(1+4i)

注意:如果復數并發賦值時,有相同的虛部或實部,那么兩個欄位賦值就退化成一個欄位,這種情況下時并發安全的,讀者可自行驗證,

4.1.3 字串(不安全)

字串在 Go 中是一個只讀位元組切片,

字串有兩個重要特點:
(1)string 可以為空(長度為 0),但不會是 nil;
(2)string物件不可以修改,

在原始碼包src/runtime/string.go我們可以找到 string 的底層資料結構:

type stringStruct struct {
	str unsafe.Pointer
	len int
}

其資料結構很簡單:
str 為字串的首地址;
len 為字串的長度(單位位元組);
string 資料結構跟切片有些類似,只不過切片還有一個表示容量的成員,事實上 string 和位元組切片間經常強制互轉,

因為 string 底層結構是個 struct,前面已經討論過 struct 并發賦值是不安全的,所以 string 的并發賦值同樣是不安全,我們來驗證一下,

func main() {
	var s string

	for i := 0; i < 1000000; i++ {
		var wg sync.WaitGroup
		// 協程 1
		wg.Add(1)
		go func() {
			defer wg.Done()
			s = "ab"
		}()

		// 協程 2
		wg.Add(1)
		go func() {
			defer wg.Done()
			s = "abc"
		}()
		wg.Wait()

		// 賦值例外判斷
		if s != "ab" && s != "abc" {
			fmt.Printf("concurrent assignment error, i=%v s=%v", i, s)
			break
		}
	}
}

運行輸出:

concurrent assignment error, i=509383 s=abi

并發賦值不出意料地出現了例外情況,推測正確,

從這里我們可以得到一個基本結論:只要底層結構是 struct 的型別,那么并發賦值都是不安全的,

注意不安全不代表一定發生錯誤,就是說不安全不代表任何并發賦值的情況下都會發生錯誤,比如上面測驗代碼回圈次數少的情況下,很難出現出現例外情況,

不過我這里想說的不是次數的問題,因為次數多少是個概率的問題,我這里說的是和所要賦的值有關,只要不同的值滿足一定特點,不管多少次并發,都是安全的,

為什么可以這么說呢,我們還是要回看 string 的底層資料結構,因為是兩個欄位,位元組指標 str 和字串長度 len,我們只要保證并發賦值情況下,兩個欄位的賦值正確就行,前面也說了,因為 struct 多個欄位的賦值是獨立,所以如果兩個欄位中只要有一個欄位是不同的,那么并發賦值就變成了一個欄位的并發賦值,這樣就不會出現問題,

比如我們并發賦值兩個等長度但內容不同的字串,就不會有問題,驗證如下:

func main() {
	var s string

	for i := 0; i < 1000000; i++ {
		var wg sync.WaitGroup
		// 協程 1
		wg.Add(1)
		go func() {
			defer wg.Done()
			s = "123"
		}()

		// 協程 2
		wg.Add(1)
		go func() {
			defer wg.Done()
			s = "abc"
		}()
		wg.Wait()

		// 賦值例外判斷
		if s != "123" && s != "abc" {
			fmt.Printf("concurrent assignment error, i=%v s=%v", i, s)
			break
		}
	}
}

上面的代碼,因為字串 123 和 abc 是等長的,所以并發賦值不管回圈多少次都是絕對的安全,因為 struct 賦值蛻變成了一個數值型指標的賦值,

4.2 復合資料型別的并發賦值

4.2.1 指標(安全)

指標是保存另一個變數的記憶體地址的變數,指標的零值為 nil,

因為是記憶體地址,所以位寬為 32位(x86平臺)或 64位(x64平臺),賦值操作由一個機器指令即可完成,不能被中斷,所以也不會出現并發賦值不安全的情況,

這在上面討論 string 等長不同值并發賦值時,已經驗證沒有問題,

4.2.2 函式(安全)

Go 函式可以像值一樣傳遞,

Go 函式定義形式如下:

func some_func_name(arguments) return_values

定義函式型別時去掉函式名:

type TypeName func(arguments) return_values

其中 TypeName 是自定義的型別名稱,

下面是一個函式型別的使用示例:

package main

import "fmt"

func main() {
	// 定義函式型別的變數
    add := func(x, y int) int {
        return x + y
    }
    fmt.Println(add(1, 2))
}

// 函式作為形參
func doOperation(fn func(int, int) int, x, y int) int {
    return fn(x, y)
}

函式型別的變數賦值時,實際上賦的是函式地址,一潭訓器指令便可以完成,所以并發賦值是安全的,

我們使用unsafe.Sizeof()可以查看函式型別的寬度(位元組),

type Add func(int, int) int
var add Add
fmt.Println(unsafe.Sizeof(add)) // 8

下面驗證一下函式變數并發賦值的安全性,

type Add func(int, int) int

func main() {
	var g Add

	var i int
	for ; i < 10000000; i++ {
		var wg sync.WaitGroup
		// 協程 1
		wg.Add(1)
		go func() {
			defer wg.Done()
			g = func(x, y int) int {
				return x + y
			}
		}()

		// 協程 2
		wg.Add(1)
		go func() {
			defer wg.Done()
			g = func(x, y int) int {
				return x - y
			}
		}()
		wg.Wait()

		// 賦值例外判斷
		if !(g(1, 1) == 2 || g(1, 1) == 0) {
			fmt.Printf("concurrent assignment error, i=%v g=%+v", i, g)
			break
		}
	}
	if i == 10000000 {
		fmt.Println("no error")
	}
}

運行輸出:

no error

4.2.2 陣列、切片、字典、通道、介面(不安全)

陣列、切片、字典、通道、介面,這些復合型別,除了陣列,其他底層資料結構都是 struct,所以并發都不是安全的,當然陣列并發賦值也是不安全的,

下面的講解不會對所有型別一一驗證,不過相關的底層資料我們應該著重了解一下,

陣列

array 是相同型別值的集合,陣列的長度是其型別的一部分,

陣列賦值和傳參都會拷貝整個陣列的資料,所以陣列不是參考型別,

陣列的底層資料結構就是其本身,是一個相同型別不同值的順序排列,所以如果陣列位寬不大于 64 位且是 2 的整數次冪(8,16,32,64),那么其并發賦值其實也是安全的,只不過這個大部分情況并非如此,所以其并發賦值是不安全的,

下面以位元組陣列為例,看下位寬不大于 64 位的并發賦值安全的情況,

func main() {
	var g [4]byte

	var i int
	for ; i < 10000000; i++ {
		var wg sync.WaitGroup
		// 協程 1
		wg.Add(1)
		go func() {
			defer wg.Done()
			g = [...]byte{1, 2, 3, 4}
		}()

		// 協程 2
		wg.Add(1)
		go func() {
			defer wg.Done()
			g = [...]byte{3, 4, 5, 6}
		}()
		wg.Wait()

		// 賦值例外判斷
		if !(g == [...]byte{1, 2, 3, 4} || g == [...]byte{3, 4, 5, 6}) {
			fmt.Printf("concurrent assignment error, i=%v g=%+v", i, g)
			break
		}
	}
	if i == 10000000 {
		fmt.Println("no error")
	}
}

運行輸出:

no error

可以看到,位寬為 32 位的陣列 [4]byte,雖然有四個元素,但是賦值時由一潭訓器指令完成,所以也是原子操作,

如果你把位元組陣列的長度換成下面這樣子,即使沒有超過 64 位,也需要多條指令完成賦值,因為 CPU 中并沒有這樣位寬的暫存器,需要拆分為多條指令來完成,

[3]byte
[5]byte
[7]byte

切片

slice 也是相同型別值的集合,只不過切片是動態調整大小的,內部是對陣列的參考,相當于動態陣列,如上所述,陣列的大小是固定的,因此切片為陣列提供了更靈活的介面,

切片是一種參考型別,它內部由三個欄位表示:

  • 陣列地址
  • 陣列長度
  • 容量大小

在原始碼包src/runtime/slice.go我們可以找到切片的底層資料結構:

type slice struct {
	array unsafe.Pointer
	len   int
	cap   int
}

因為其是一個 struct,所以并發賦值是不安全的,這里不再以代碼驗證,

字典

map 是經常被使用的內置 key-value 型容器,是一個同型別元素的無序組,元素通過另一型別唯一鍵進行索引,

map 的底層結構也是一個 struct,定義于src/runtime/map.go

// A header for a Go map.
type hmap struct {
	// Note: the format of the hmap is also encoded in cmd/compile/internal/gc/reflect.go.
	// Make sure this stays in sync with the compiler's definition.
	count     int // # live cells == size of map.  Must be first (used by len() builtin)
	flags     uint8
	B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
	noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
	hash0     uint32 // hash seed

	buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
	oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
	nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)

	extra *mapextra // optional fields
}

map 并發讀寫會引發 panic,一般使用讀寫鎖 sync.RWMutex 來保證安全,

通道

channel 在 goroutine 之間提供同步和通信,您可以將其視為 goroutines 通過其發送值和接收值的管道,運算子<-用于發送或接收資料,箭頭方向指定資料流的方向,

ch <- val    	// Sending a value present in var variable to channel
val := <-cha	// Receive a value from  the channel and assign it to val variable

因為 channel 通常用法是初始化后作為共享變數在 goroutine 之間提供同步和通信,很少會發生賦值,就是把一個 channel 賦給另一個 channel,所以這里就不過多討論其并發賦值的安全性,如果真的有這種情況,那么只要知道其底層資料結構是個 struct,并發賦值時不安全的即可,

關于 channel 的底層資料介面可在 Go 原始碼src\runtime\chan.go

type hchan struct {
	qcount   uint           // total data in the queue
	dataqsiz uint           // size of the circular queue
	buf      unsafe.Pointer // points to an array of dataqsiz elements
	elemsize uint16
	closed   uint32
	elemtype *_type // element type
	sendx    uint   // send index
	recvx    uint   // receive index
	recvq    waitq  // list of recv waiters
	sendq    waitq  // list of send waiters

	// lock protects all fields in hchan, as well as several
	// fields in sudogs blocked on this channel.
	//
	// Do not change another G's status while holding this lock
	// (in particular, do not ready a G), as this can deadlock
	// with stack shrinking.
	lock mutex
}

關于 channel 的用法和實作原理,感興趣的同學可自行查閱資料探究,這里不再贅述,

介面

介面是 Go 中的一個型別,它是方法的集合,實作介面的所有方法的任何型別都屬于該介面型別,介面的零值為 nil,

定義一個介面型別的變數后,如果具體型別實作了介面的所有方法,我們可以將任何具體型別的值賦給這個變數,

實際上 Go 中的介面有個特殊情況,就是空介面,其不包含任何方法,因此,默認情況下,所有具體型別都實作空介面,

如果撰寫的函式接受空介面,則可以向該函式傳遞任何型別,

package main

import "fmt"

func main() {
    test("thisisstring")
    test("10")
    test(true)
}

func test(a interface{}) {
    fmt.Printf("(%v, %T)\n", a, a)
}

運行輸出:

(thisisstring, string)
(10, string)
(true, bool)

因為存在兩種型別的介面,包含方法的非空介面和不包含任何方法的空介面,所以在底層實作上使用runtime.iface表示非空介面,使用runtime.eface表示空介面 interface{},

在 Go 原始碼中 runtime 包下,我們可以找到 runtime.iface 和 runtime.eface 的定義,

type iface struct { // 16 位元組
	tab  *itab
	data unsafe.Pointer
}

這個結構體中有指向原始資料的指標 data 和 runtime.itab,

runtime.itab 結構體是介面型別的核心組成部分,每一個 runtime.itab 都占 32 位元組,我們可以將其看成介面型別和具體型別的組合,它們分別用 inter 和 _type 兩個欄位表示:

type itab struct { // 32 位元組
	inter *interfacetype
	_type *_type
	hash  uint32
	_     [4]byte
	fun   [1]uintptr
}

除了 inter 和 _type 兩個用于表示型別的欄位之外,上述結構體中的另外兩個欄位也有自己的作用:
hash 是對 _type.hash 的拷貝,當我們想將 interface 型別轉換成具體型別時,可以使用該欄位快速判斷目標型別和具體型別 runtime._type 是否一致;
fun 是一個動態大小的陣列,它是一個用于動態派發的虛函式表,存盤了一組函式指標,雖然該變數被宣告成大小固定的陣列,但是在使用時會通過原始指標獲取其中的資料,所以 fun 陣列中保存的元素數量是不確定的,

type eface struct { // 16 位元組
	_type *_type
	data  unsafe.Pointer
}

由于 interface{} 型別不包含任何方法,所以它的結構也相對來說比較簡單,只包含指向底層資料和型別的兩個指標,從上述結構我們也能推斷出 Go 語言的任意型別都可以轉換成 interface{},

其中runtime._type是 Go 語言型別的運行時表示,下面是運行時包中的結構體,其中包含了很多型別的元資訊,例如:型別的大小、哈希、對齊以及種類等,

type _type struct {
	size       uintptr
	ptrdata    uintptr
	hash       uint32
	tflag      tflag
	align      uint8
	fieldAlign uint8
	kind       uint8
	equal      func(unsafe.Pointer, unsafe.Pointer) bool
	gcdata     *byte
	str        nameOff
	ptrToThis  typeOff
}

size 欄位存盤了型別占用的記憶體空間,為記憶體空間的分配提供資訊;
hash 欄位能夠幫助我們快速確定型別是否相等;
equal 欄位用于判斷當前型別的多個物件是否相等,該欄位是為了減少 Go 語言二進制包大小從 typeAlg 結構體中遷移過來的,

我們只需要對 runtime._type 結構體中的欄位有一個大體的概念,不需要詳細理解所有欄位的作用和意義,

根據上面對介面底層結構的分析,我們可以得出如下結論:

介面底層資料結構包含兩個欄位,相互賦值時如果是相同具體型別不同值并發賦給一個介面,那么只有一個欄位 data 的值是不同的,此時退化成指標的并發賦值,所以是安全的,但如果是不同具體型別的值并發賦給一個介面,那么并引發 panic,

不同具體型別并發賦值介面非安全驗證如下:

func main() {
	var g interface{}

	var i int
	for ; i < 10000000; i++ {
		var wg sync.WaitGroup
		// 協程 1
		wg.Add(1)
		go func() {
			defer wg.Done()
			g = "a"
		}()

		// 協程 2
		wg.Add(1)
		go func() {
			defer wg.Done()
			g = 1
		}()
		wg.Wait()

		// 賦值例外判斷
		v1, _ := g.(string)
		v2, _ := g.(int)
		if !(v1 == "a" || v2 == 1) {
			fmt.Printf("concurrent assignment error, i=%v g=%v ", i, g)
			break
		}
	}
	if i == 10000000 {
		fmt.Println("no error")
	}
}

運行輸出:

unexpected fault address 0x1fffffa8
fatal error: fault
[signal 0xc0000005 code=0x0 addr=0x1fffffa8 pc=0x5f5585]
...

把上面的示例代碼中協議 1 中的字串換成一個 int 值,那么并發是安全的,

func main() {
	var g interface{}

	var i int
	for ; i < 10000000; i++ {
		var wg sync.WaitGroup
		// 協程 1
		wg.Add(1)
		go func() {
			defer wg.Done()
			g = 0
		}()

		// 協程 2
		wg.Add(1)
		go func() {
			defer wg.Done()
			g = 1
		}()
		wg.Wait()

		// 賦值例外判斷
		v1, _ := g.(int)
		v2, _ := g.(int)
		if !(v1 == 0 || v2 == 1) {
			fmt.Printf("concurrent assignment error, i=%v g=%v ", i, g)
			break
		}
	}
	if i == 10000000 {
		fmt.Println("no error")
	}
}

運行輸出:

no error

5.小結

Go 多協程并發的場景無處不在,并發對同一變數的賦值也是經常遇到,本文嘗試探討了 Go 中所有型別并發賦值的安全性,

(1)由一潭訓器指令完成賦值的型別并發賦值是安全的,這些型別有:位元組型,布爾型、整型、浮點型、字符型、指標、函式,

(2)陣列由一個或多個元素組成,大部分情況并發不安全,注意:當位寬不大于 64 位且是 2 的整數次冪(8,16,32,64),那么其并發賦值是安全的,

(3)struct 或底層是 struct 的型別并發賦值大部分情況并發不安全,這些型別有:復數、字串、 陣列、切片、字典、通道、介面,注意:當 struct 賦值時退化為單個欄位由一個機器指令完成賦值時,并發賦值又是安全的,這種情況有:
(a)實部或虛部相同的復數的并發賦值;
(b)等長字串的并發賦值;
(c)同長度同容量切片的并發賦值;
(d)同一種具體型別不同值并發賦給介面,

注意: 以上結論基于x86-64指令集架構,其他平臺未作實際驗證,


參考文獻

[1] 簡書.Golang并發操作變數需要注意的問題
[2] All data types in Golang with examples
[3] The Go Blog.Go Slices: usage and internals
[4] CSDN.Golang map 三板斧第三式:實作原理
[5] CSDN.Golang channel 快速入門
[6] Go 語言設計與實作.4.2介面

轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/272550.html

標籤:區塊鏈

上一篇:【精選】3060顯卡挖礦算力,3060挖礦算力破解,3060算力破解

下一篇:一個庫解決flutter串列側滑選單,仿微信確認洗掉效果,串列編輯效果等

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • JAVA使用 web3j 進行token轉賬

    最近新學習了下區塊鏈這方面的知識,所學不多,給大家分享下。 # 1. 關于web3j web3j是一個高度模塊化,反應性,型別安全的Java和Android庫,用于與智能合約配合并與以太坊網路上的客戶端(節點)集成。 # 2. 準備作業 jdk版本1.8 引入maven <dependency> < ......

    uj5u.com 2020-09-10 03:03:06 more
  • 以太坊智能合約開發框架Truffle

    前言 部署智能合約有多種方式,命令列的瀏覽器的渠道都有,但往往跟我們程式員的風格不太相符,因為我們習慣了在IDE里寫了代碼然后打包運行看效果。 雖然現在IDE中已經存在了Solidity插件,可以撰寫智能合約,但是部署智能合約卻要另走他路,沒辦法進行一個快捷的部署與測驗。 如果團隊管理的區塊節點多、 ......

    uj5u.com 2020-09-10 03:03:12 more
  • 谷歌二次驗證碼成為區塊鏈專用安全碼,你怎么看?

    前言 谷歌身份驗證器,前些年大家都比較陌生,但隨著國內互聯網安全的加強,它越來越多地出現在大家的視野中。 比較廣泛接觸的人群是國際3A游戲愛好者,游戲盜號現象嚴重+國外賬號安全應用廣泛,這類游戲一般都會要求用戶系結名為“兩步驗證”、“雙重驗證”等,平臺一般都推薦用谷歌身份驗證器。 后來區塊鏈業務風靡 ......

    uj5u.com 2020-09-10 03:03:17 more
  • 密碼學DAY1

    目錄 ##1.1 密碼學基本概念 密碼在我們的生活中有著重要的作用,那么密碼究竟來自何方,為何會產生呢? 密碼學是網路安全、資訊安全、區塊鏈等產品的基礎,常見的非對稱加密、對稱加密、散列函式等,都屬于密碼學范疇。 密碼學有數千年的歷史,從最開始的替換法到如今的非對稱加密演算法,經歷了古典密碼學,近代密 ......

    uj5u.com 2020-09-10 03:03:50 more
  • 密碼學DAY1_02

    目錄 ##1.1 ASCII編碼 ASCII(American Standard Code for Information Interchange,美國資訊交換標準代碼)是基于拉丁字母的一套電腦編碼系統,主要用于顯示現代英語和其他西歐語言。它是現今最通用的單位元組編碼系統,并等同于國際標準ISO/IE ......

    uj5u.com 2020-09-10 03:04:50 more
  • 密碼學DAY2

    ##1.1 加密模式 加密模式:https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html ECB ECB : Electronic codebook, 電子密碼本. 需要加密的訊息按照塊密碼的塊大小被分為數個塊,并對每個塊進 ......

    uj5u.com 2020-09-10 03:05:42 more
  • NTP時鐘服務器的特點(京準電子)

    NTP時鐘服務器的特點(京準電子) NTP時鐘服務器的特點(京準電子) 京準電子官V——ahjzsz 首先對時間同步進行了背景介紹,然后討論了不同的時間同步網路技術,最后指出了建立全球或區域時間同步網存在的問題。 一、概 述 在通信領域,“同步”概念是指頻率的同步,即網路各個節點的時鐘頻率和相位同步 ......

    uj5u.com 2020-09-10 03:05:47 more
  • 標準化考場時鐘同步系統推進智能化校園建設

    標準化考場時鐘同步系統推進智能化校園建設 標準化考場時鐘同步系統推進智能化校園建設 安徽京準電子科技官微——ahjzsz 一、背景概述隨著教育事業的快速發展,學校建設如雨后春筍,隨之而來的學校教育、管理、安全方面的問題成了學校管理人員面臨的最大的挑戰,這些問題同時也是學生家長所擔心的。為了讓學生有更 ......

    uj5u.com 2020-09-10 03:05:51 more
  • 位元幣入門

    引言 位元幣基本結構 位元幣基礎知識 1)哈希演算法 2)非對稱加密技術 3)數字簽名 4)MerkleTree 5)哪有位元幣,有的是UTXO 6)位元幣挖礦與共識 7)區塊驗證(共識) 總結 引言 上一篇我們已經知道了什么是區塊鏈,此篇說一下區塊鏈的第一個應用——位元幣。其實先有位元幣,后有的區塊 ......

    uj5u.com 2020-09-10 03:06:15 more
  • 北斗對時服務器(北斗對時設備)電力系統應用

    北斗對時服務器(北斗對時設備)電力系統應用 北斗對時服務器(北斗對時設備)電力系統應用 京準電子科技官微(ahjzsz) 中國北斗衛星導航系統(英文名稱:BeiDou Navigation Satellite System,簡稱BDS),因為是目前世界范圍內唯一可以大面積提供免費定位服務的系統,所以 ......

    uj5u.com 2020-09-10 03:06:20 more
最新发布
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:46:47 more
  • Hyperledger Fabric 使用 CouchDB 和復雜智能合約開發

    在上個實驗中,我們已經實作了簡單智能合約實作及客戶端開發,但該實驗中智能合約只有基礎的增刪改查功能,且其中的資料管理功能與傳統 MySQL 比相差甚遠。本文將在前面實驗的基礎上,將 Hyperledger Fabric 的默認資料庫支持 LevelDB 改為 CouchDB 模式,以實作更復雜的資料... ......

    uj5u.com 2023-04-16 07:28:31 more
  • .NET Core 波場鏈離線簽名、廣播交易(發送 TRX和USDT)筆記

    Get Started NuGet You can run the following command to install the Tron.Wallet.Net in your project. PM> Install-Package Tron.Wallet.Net 配置 public reco ......

    uj5u.com 2023-04-14 08:08:00 more
  • DKP 黑客分析——不正確的代幣對比率計算

    概述: 2023 年 2 月 8 日,針對 DKP 協議的閃電貸攻擊導致該協議的用戶損失了 8 萬美元,因為 execute() 函式取決于 USDT-DKP 對中兩種代幣的余額比率。 智能合約黑客概述: 攻擊者的交易:0x0c850f,0x2d31 攻擊者地址:0xF38 利用合同:0xf34ad ......

    uj5u.com 2023-04-07 07:46:09 more
  • Defi開發簡介

    Defi開發簡介 介紹 Defi是去中心化金融的縮寫, 是一項旨在利用區塊鏈技術和智能合約創建更加開放,可訪問和透明的金融體系的運動. 這與傳統金融形成鮮明對比,傳統金融通常由少數大型銀行和金融機構控制 在Defi的世界里,用戶可以直接從他們的電腦或移動設備上訪問廣泛的金融服務,而不需要像銀行或者信 ......

    uj5u.com 2023-04-05 08:01:34 more
  • solidity簡單的ERC20代幣實作

    // SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0 <0.9.0; import "hardhat/console.sol"; //ERC20 同質化代幣,每個代幣的本質或性質都是相同 //ETH 是原生代幣,它不是ERC20代幣, ......

    uj5u.com 2023-03-21 07:56:29 more
  • solidity 參考型別修飾符memory、calldata與storage 常量修飾符C

    在solidity語言中 參考型別修飾符(參考型別為存盤空間不固定的數值型別) memory、calldata與storage,它們只能修飾參考型別變數,比如字串、陣列、位元組等... memory 適用于方法傳參、返參或在方法體內使用,使用完就會清除掉,釋放記憶體 calldata 僅適用于方法傳參 ......

    uj5u.com 2023-03-08 07:57:54 more
  • solidity注解標簽

    在solidity語言中 注釋符為// 注解符為/* 內容*/ 或者 是 ///內容 注解中含有這幾個標簽給予我們使用 @title 一個應該描述合約/介面的標題 contract, library, interface @author 作者的名字 contract, library, interf ......

    uj5u.com 2023-03-08 07:57:49 more
  • 評價指標:相似度、GAS消耗

    【代碼注釋自動生成方法綜述】 這些評測指標主要來自機器翻譯和文本總結等研究領域,可以評估候選文本(即基于代碼注釋自動方法而生成)和參考文本(即基于手工方式而生成)的相似度. BLEU指標^[^?88^^?^]^:其全稱是bilingual evaluation understudy.該指標是最早用于 ......

    uj5u.com 2023-02-23 07:27:39 more
  • 基于NOSTR協議的“公有制”版本的Twitter,去中心化社交軟體Damus

    最近,一個幽靈,Web3的幽靈,在網路游蕩,它叫Damus,這玩意詮釋了什么叫做病毒式營銷,滑稽的是,一個Web3產品卻在Web2的產品鏈上瘋狂傳銷,各方大佬紛紛為其背書,到底發生了什么?Damus的葫蘆里,賣的是什么藥? 注冊和簡單實用 很少有什么產品在用戶注冊環節會有什么噱頭,但Damus確實出 ......

    uj5u.com 2023-02-05 06:48:39 more