day 2从Mutex继续开始

本文详细介绍了Go语言中sync包的使用,包括Mutex、RWMutex、Once和Map的并发控制,以及io包的Reader、Writer、Closer和Seeker接口。此外,还探讨了文件操作、网络编程(如HTTP和WebSocket)的相关内容。
摘要由CSDN通过智能技术生成
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包提供了四个基础接口,它们分别是:

  1. Reader接口:表示一个可以从其中读取数据的源。它定义了Read(p []byte) (n int, err error)方法,用于从源中读取数据并返回读取的字节数和可能的错误。

  2. Writer接口:表示一个可以向其写入数据的目标。它定义了Write(p []byte) (n int, err error)方法,用于向目标写入数据并返回写入的字节数和可能的错误。

  3. Closer接口:表示一个可以关闭的资源。它定义了Close() error方法,用于关闭资源并返回可能的错误。

  4. Seeker接口:表示一个可以在其中查找位置的源。它定义了Seek(offset int64, whence int) (int64, error)方法,用于在源中移动读取位置并返回新的位置和可能的错误。

这些接口在 Go 的 I/O 操作中非常重要,它们允许不同的数据源、数据目标和资源以统一的方式进行操作,提供了灵活性和可扩展性。许多具体的 I/O 类型(如文件、网络连接、缓冲区等)都会实现这些接口中的一个或多个,以便与其他 I/O 操作进行交互和组合。

例如,os.File类型实现了ReaderWriterCloser接口,bytes.Buffer类型实现了ReaderWriter接口,而net.Conn类型实现了ReaderWriter接口。

通过使用这些接口,你可以编写通用的 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的升级

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值