sync包的使用
事实上,我们在讨论一个锁的时候,不仅仅包含锁本身包含的东西,也包含它所指向的物体
package main
import (
"fmt"
"sync"
)
func SyncClass() {
/*l := &sync.Mutex{}
//记住要用&,因为我们要保证异步的锁是同一把*/
l := &sync.RWMutex{}
go readLockFun(l)
go readLockFun(l)
go lockFunc(l)
go lockFunc(l)
go readLockFun(l)
for {
} //这个用于等待
//防止主线程结束
}
func lockFunc(lock *sync.RWMutex) {
lock.Lock() //写的时候排斥其他的写锁和读锁
fmt.Println("疯狂学习")
lock.Unlock()
}
func readLockFun(lock *sync.RWMutex) {
lock.RLock() //在读取的时候 不会阻塞其他的读锁,但是会排斥写锁
//就是readLockFun会并发,但是lockFunc串行
fmt.Println("study hard")
lock.RUnlock()
}
/*
其实这段代码中的l,是协程在争夺一把锁和其所指向的空间
*/
func SyncClass() {
o := &sync.Once{} //只执行一次
for i := 0; i < 10; i++ {
o.Do(func() {
fmt.Println(1)
})
}
}
sync.Once 类型的对象只能执行其内部的函数一次,无论它被调用多少次。这是通过 sync.Once 类型内部的一个 flag 来实现的。
package main
import (
"fmt"
"sync"
"time"
)
func SyncClass() {
m := &sync.Map{}
go func() {
for {
m.Store(1, 1)//这里就是写入的意思
}
}()
go func() {
for {
fmt.Println(m.Load(1))//m.Load就是读取数据的意思
}
}()
time.Sleep(1 * time.Second)
}
func main() {
SyncClass()
}
sync.Map是并发安全的,即使两个goroutine同时对其进行读写操作,也能保证它的安全,读写是分开进行的
package main
import (
"fmt"
"sync"
"time"
)
func SyncClass() {
m := &sync.Map{}
go func() {
for {
m.Store(1, 1)
m.Delete(1) //删除之后m.Load也还是能得出
}
}()
go func() {
for {
fmt.Println(m.Load(1))
}
}()
time.Sleep(1 * time.Second)
}
func main() {
SyncClass()
}
func SyncClass() {
m := &sync.Map{}
m.Store(1, 1)
m.Store(2, 2)
m.Store(3, 3)
m.Range(func(key, value any) bool {
fmt.Println(key, value)
return true //如果return false 就走不下去
//return true表示继续遍历下个键值对
})
}
func SyncClass() {
p := &sync.Pool{}
/*
可以放很多东西在这个池子里面
*/
p.Put(1)
p.Put(2)
p.Put(3)
p.Put(4)
p.Put(5)
p.Put(6)
p.Put(7)
for i:=0;i<6;i++{
time.Sleep(1*time.Second)
fmt.Println(p.Get())
}
}
func SyncClass() {
co := sync.NewCond(&sync.Mutex{})
go func() {
co.L.Lock()
fmt.Println("lock1")
co.Wait()
fmt.Println("unlock1")
co.L.Unlock()
}()
go func() {
co.L.Lock()
fmt.Println("lock2")
co.Wait()
fmt.Println("unlock2")
co.L.Unlock()
}()
time.Sleep(2 * time.Second)
co.Broadcast() //给全部锁发通知
time.Sleep(1 * time.Second)
}
文件操作
io包 四个接口
在 Go 语言中,io
包提供了四个基础接口,它们分别是:
-
Reader
接口:表示一个可以从其中读取数据的源。它定义了Read(p []byte) (n int, err error)
方法,用于从源中读取数据并返回读取的字节数和可能的错误。 -
Writer
接口:表示一个可以向其写入数据的目标。它定义了Write(p []byte) (n int, err error)
方法,用于向目标写入数据并返回写入的字节数和可能的错误。 -
Closer
接口:表示一个可以关闭的资源。它定义了Close() error
方法,用于关闭资源并返回可能的错误。 -
Seeker
接口:表示一个可以在其中查找位置的源。它定义了Seek(offset int64, whence int) (int64, error)
方法,用于在源中移动读取位置并返回新的位置和可能的错误。
这些接口在 Go 的 I/O 操作中非常重要,它们允许不同的数据源、数据目标和资源以统一的方式进行操作,提供了灵活性和可扩展性。许多具体的 I/O 类型(如文件、网络连接、缓冲区等)都会实现这些接口中的一个或多个,以便与其他 I/O 操作进行交互和组合。
例如,os.File
类型实现了Reader
、Writer
和Closer
接口,bytes.Buffer
类型实现了Reader
和Writer
接口,而net.Conn
类型实现了Reader
和Writer
接口。
通过使用这些接口,你可以编写通用的 I/O 操作函数,而不依赖于特定的 I/O 类型,从而提高代码的可重用性和灵活性。这样,你可以根据具体的需求选择不同的实现来处理各种 I/O 任务。
// Writer 是包装基本 Write 方法的接口。
//
// Write 将 p 中的 len(p) 个字节写入底层数据流。
// 它返回从 p 写入的字节数 (0 <= n <= len(p))
// 以及遇到导致写入提前停止的任何错误。
// 如果 Write 返回 n < len(p),则必须返回非零错误。
// 写入不得修改切片数据,即使是暂时的。
//
// 实现不得保留 p。
type Writer interface {
Write(p []byte) (n int, err error)
//将p中的字节写入,返回len(p)个字节
}
// Closer 是包装基本 Close 方法的接口。
//
// 第一次调用后 Close 的行为未定义。
// 具体的实现可能会记录它们自己的行为。
type Closer interface {
Close() error
}
// Reader 是包装基本 Read 方法的接口。
//
// Read 将最多 len(p) 个字节读取到 p 中。 它返回字节数
// 读取 (0 <= n <= len(p)) 以及遇到的任何错误。 即使读过
// 返回 n < len(p),它可能在调用期间使用所有 p 作为暂存空间。
// 如果有数据可用但不是 len(p) 个字节,则按常规读取
// 返回可用的内容,而不是等待更多。
// 当 Read 遇到错误或文件结束条件后
// 成功读取 n > 0 字节,返回数量
// 读取的字节数。 它可能会从同一调用返回(非零)错误
// 或从后续调用中返回错误(且 n == 0)。
// 这种一般情况的一个例子是 Reader 返回
// 输入流末尾的字节数可能非零
// 返回 err == EOF 或 err == nil。 下一篇阅读应该
// 返回 0,EOF。
//
// 调用者应该始终处理之前返回的 n > 0 个字节
// 考虑错误 err. 这样做可以正确处理 I/O 错误
// 读取一些字节以及两个字节后发生的情况
// 允许的 EOF 行为。
//
// 如果 len(p) == 0,Read 应始终返回 n == 0。它可能返回 a
// 如果已知某些错误条件(例如 EOF),则出现非零错误。
//
// 不鼓励 Read 的实现返回 a
// 零字节数且出现 nil 错误,除非 len(p) == 0。
// 调用者应该将返回 0 和 nil 视为表明
// 什么都没发生; 特别是它不指示 EOF。
// 实现不得保留 p。
type Reader interface {
Read(p []byte) (n int, err error)
//读取到 p中,并且返回读取成功 的字节数
}
os.OpenFile()
这个是os.Open和其他方法的基础
func main() {
f, err := os.OpenFile("./test.txt", os.O_CREATE|os.O_RDWR, 0777)
if err != nil {
fmt.Println(err)
return
}
b := make([]byte, 10)
f.Read(b)
fmt.Println(b)
}
// Seek 设置文件下一个读取或写入的偏移量, // 根据来源进行解释:0 表示相对于文件的原点,1 表示相对于当前偏移量,2 表示相对于结尾。 // 它返回新的偏移量和错误(如果有)。 // 未指定使用 O_APPEND 打开的文件上的 Seek 行为。
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
f, err := os.OpenFile("./test.txt", os.O_CREATE|os.O_RDWR, 0777)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
//缓存读的文件
reader := bufio.NewReader(f)
for {
str, isPrefix, err := reader.ReadLine() //如果为false,表示当前的行已经结束
/*
当前读取的行尚未结束的情况包括:
行的内容超过了 Reader 缓冲区的大小,因此需要多次调用 ReadLine 才能完全读取该行的内容。
读取到的行以换行符 \n 结尾,但是该换行符是行的一部分,而不是行的结束标志。
*/
if err != nil {
fmt.Println(err.Error(), isPrefix)
return
}
fmt.Println(string(str), isPrefix)
}
}
b, _ := ioutil.ReadAll(f) //读取全部文件
fmt.Println(string((b)))
file, _ := os.ReadFile("./test.txt")//这个也是一样的,尽量使用这个包
fmt.Println(string(file))
func file() {
d, err := os.ReadDir("./")//从当前根目录读取文件夹
if err != nil {
fmt.Println(err)
} //
for _, v := range d {
fmt.Println(v.Info())
fmt.Println(v.Name())
fmt.Println(v.IsDir())//判断是不是文件夹
fmt.Println(v.Type())
}
}
func file() {
f, err := os.OpenFile("test.txt", os.O_CREATE|os.O_RDWR, 0777)
if err != nil {
fmt.Println(err)
return
}
defer f.Close() //一定要加,不然所有的write没有效果
//writer := bufio.NewWriter(f)
reader := bufio.NewReader(f)
for {
str, err := reader.ReadString('\n')
if err != nil {
fmt.Println(err)
return
}
_, err = f.Write([]byte("1" + str))
if err != nil {
fmt.Println(err)
return
}
//writer.WriteString(str)
}
}
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
file()
}
func file() {
f, err := os.OpenFile("test.txt", os.O_CREATE|os.O_RDWR, 0777)
if err != nil {
fmt.Println(err)
return
}
defer f.Close() //一定要加,不然所有的write没有效果
writer := bufio.NewWriter(f)
reader := bufio.NewReader(f)
i := 0
for {
i++
str, err := reader.ReadString('\n')
if err != nil {
break//这里一定要注意督导文件末尾的时候一定要退出循环
}
writer.WriteString(strconv.Itoa(i) + str)
}
writer.Flush()
}
// Copy函数从src拷贝数据到dst,直到src达到文件末尾或者发生错误为止。
// 它返回拷贝的字节数和在拷贝过程中遇到的第一个错误(如果有的话)。
//
// 一个成功的拷贝返回err == nil,而不是err == EOF。
// 因为Copy函数的定义是从src读取数据直到文件末尾,它不会将从Read中返回的EOF视为需要报告的错误。
//
// 如果src实现了[WriterTo]接口,
// 则拷贝操作通过调用src.WriteTo(dst)来实现。
// 否则,如果dst实现了[ReaderFrom]接口,
// 则拷贝操作通过调用dst.ReadFrom(src)来实现。
func Copy(dst Writer, src Reader) (written int64, err error) {
return copyBuffer(dst, src, nil)
}
因为感觉迷迷糊糊的,所以又看了一遍这个文件操作的视频
net包
一般都用框架实现了
package main
import (
"bufio"
"fmt"
"net"
"os"
)
func main() {
tcpAddr, _ := net.ResolveTCPAddr("tcp", ":8888")
/*
注意一下,这里address需要打个:表示端口号
*/
conn, _ := net.DialTCP("tcp", nil, tcpAddr)
reader := bufio.NewReader(os.Stdin)
for {
bytes, _, _ := reader.ReadLine()
conn.Write(bytes)
rb := make([]byte, 1024)
rn, _ := conn.Read(rb) //rn表示都回来的字符数,噶送过来的信息会存入到rb
fmt.Println(string(rb[0:rn]))
}
}
package main
import (
"fmt"
"net"
)
func main() {
tcpAddr, _ := net.ResolveTCPAddr("tcp", ":8888")
listener, _ := net.ListenTCP("tcp", tcpAddr)
for {
//因为连接这个端口的可能不止一个
conn, err := listener.AcceptTCP()
if err != nil {
fmt.Println(err)
return
}
go handlerConnection(conn)
}
}
func handlerConnection(conn *net.TCPConn) {
for {
buf := make([]byte, 1024)
n, err := conn.Read(buf)
/*
这里要有一个err来判断EOF是否存活(来自通信端)
*/
if err != nil {
fmt.Println(err)
break
}
fmt.Println(conn.RemoteAddr().String() + string((buf[:n])))
str := "收到了" + string(buf[:n])
conn.Write([]byte(str))
}
}
http学了很多遍了,就直接过了
RPC包
本地调用远程方法,允许一个程序调用另一个程序定义的函数或方法,这些函数或方法可能在不同的计算机上进行,RPC通过网络调用另一个程序中的函数
package main
import (
"fmt"
"net"
"net/http"
"net/rpc"
"time"
)
type Server struct {
}
type Req struct {
NumOne int
NumTwo int
}
type Res struct {
Num int
}
func (s *Server) Add(req Req, res *Res) error {
time.Sleep(10 * time.Second)
res.Num = req.NumTwo + req.NumOne
return nil
}
func (s *Server) GetName(req Req, res Res) {
}
func main() {
//首先我们要调用
rpc.Register(new(Server)) //需要符合规范
rpc.HandleHTTP()
l, e := net.Listen("tcp", ":8888")
if e != nil {
fmt.Println("yoou're wrong")
}
http.Serve(l, nil) //有一个默认handle
}
package main
import (
"fmt"
"log"
"time"
"net/rpc"
)
type Server struct {
}
type Req struct {
NumOne int
NumTwo int
}
type Res struct {
Num int
}
func (s *Server) GetName(req Req, res Res) {
}
func main() {
req := Req{NumOne: 1, NumTwo: 2}
var res Res
client, err := rpc.DialHTTP("tcp", "localhost:8888")
if err != nil {
log.Fatal("dialing:", err)
}
ca := client.Go("Server.Add", req, &res, nil)
fmt.Println("我希望在这里做一些事情")
/*
是同步还是异步?
异步->所以我们需要在等待过程中去做一些别的事情
这个时候我们可以使用select语句
*/
for {
select {
case call := <-ca.Done:
fmt.Println(call.Reply)
return
default:
time.Sleep(1 * time.Second)
fmt.Println("我等着")
}
}
}
go泛型
package main
import "fmt"
func test[T any](i T) T {
/*
想要把他变成泛型,就在标识后面加上T
*/
return i
}
func main() {
fmt.Println(test[string]("123")+"456")
fmt.Print(test[int](123)+456)
}
package main
import "fmt"
type my[T any] struct{
/*
因为它不能暴露,所以我们的结构体首字母小写
*/
Name T
}
func main() {
m:= my[int]{
Name:123,
}
fmt.Println(m)
}
package main
import "fmt"
type myC[A any] []A
func main() {
c := myC[int]{12, 3, 4}
fmt.Println(c)
}
package main
import "fmt"
//约束
type MyType interface{
getValue() string
}
func test[T MyType](t T){//规定我传入的数据结构需要实现getValue这个方法
fmt.Println(t.getValue())//后面传入的参数也就是这个数据结构的实例
}
type my struct{
Name string
}
func(m my) getValue()string{
return m.Name
}
func main() {
m := my{Name: "奇淼"}
test[my](m)//感觉很神奇
}
package main
import "fmt"
//约束
type MyType interface{
getValue() string
}
func test[T MyType](t T){//规定我传入的数据结构需要实现getValue这个方法
fmt.Println(t.getValue())//后面传入的参数也就是这个数据结构的实例
}
type my string
func(m my) getValue()string{
return "456"
}
func main() {
var m my
m = "8888"
test[my](m)
/*
这里需要注意的是
我们需要定义变量为my,my不是string
*/
}
package main
import "fmt"
/*
T的传递
*/
type my[T any] struct{
Name string
}
func (m my[T]) getValue(t T) T{
fmt.Print(t)
return t
}
func main() {
m := my[string]{Name:"qimiao"}
m.getValue("qimiap")
}
go websocket
websocket是http的升级