zoukankan      html  css  js  c++  java
  • 【转】golang 并发程序写入map两种实现方式sync.Mutex和chan的效率对比

    原文:https://www.cnblogs.com/shuiyuejiangnan/p/9722791.html#4207785

    学会用gdb 调试程序,查看程序的运行过程,各个变量的值

    -----------------------------------------------------------------------------------------------------------------------

    golang原生的数据结构map,由于是通过hash方式实现的,不支持并发写入,但是在golang很多并发场景中,不可避免的需要写入map,下面介绍两种解决map并发写入的实现方式:

    1. sync.Mutex互斥锁(通过加锁解锁解决map不能并发写入的问题)
    2. chan (通过管道来解决map并发的问题),chan的存在完美解决goroutine之间的通信以及数据race问题,但是它的性能如何呢,下面让我们来测一下

    首先先上代码:

    package main

    import (
    "testing"
    "sync"
    )

    func BenchmarkTest(b *testing.B) {
    var m = make(map[int]int, 10000)
    var mu = &sync.Mutex{}
    var c = make(chan int, 200)
    var w = &sync.WaitGroup{}

    w.Add(b.N)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
    c <- 1
    go func(index int) {
    mu.Lock()
    m[index] = 0
    mu.Unlock()
    w.Done()
    <- c
    }(i)
    }
    w.Wait()
    }

    func BenchmarkTestBlock(b *testing.B) {
    var m = make(map[int]int, 10000)
    var c = make(chan int, 200)
    var dataChan = make(chan int, 200)
    var w = &sync.WaitGroup{}

    w.Add(b.N+1)
    go func() {
    for i := 0; i < b.N; i++ {
    m[<- dataChan] = 0
    }
    w.Done()
    }()

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
    c<-1
    go func(index int) {
    dataChan <- index
    w.Done()
    <-c
    }(i)
    }
    w.Wait()
    }

    代码很简洁,我就不多解释了。

    看一下执行效率    

    可以看到,chan的实现方式其性能并不理想,比sync.Mutex的实现方式的效率低了大约25%左右。 

    chan的效率为什么不理想呢,其实原因很简单,下面看一下它的数据结构就一目了然了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package main
     
    import (
        "fmt"
    )
     
    func main() {
        s := "hello, world!"
        b := []byte(s)
        c := make(chan int, 1)
        c<-1
        fmt.Println(s, b, <-c)
    }

    我们可以看到channel的实现是通过互斥锁和数组的方式实现的,而且还有一些其他字段的同步,因此它的效率是不会比直接用互斥锁更快的。

    很多看上去很高端很简洁的东西,其实是编辑器和runtime在后面为我们码农实现,当然与之相来是额外的内存和时间开销。

    --------------------------------------------------------------------------------------------------------------

    . gdb 运行初始化命令 -x usercmd

    在.gdbinit 初始化命令中, 可以用#(同bash 注释符合)来注释

    可以用source 在启动gdb 后运行gdb 命令文件

    可以用file 在启动gdb 后再加载文件

    2. gdb 传递命令行 参数
     a. run 
     b. set args  
    可用 show args 查看
    3. gdb 中宏的操作
    默认编译的时候,调试过程是看不见宏的值的。编译时候需要给选项。-g3
    gcc  -g3 -o test.c  test
    gdb test
    察看宏(macro)命令: p macroname(...)  
    就像察看一个普通变量一样。如果只想看宏的展开形势,就用如下命令:
    macro expand macroname(...) 查看展开形式

    还可以用info macro macroname 查看宏定义。

    4. gdb 临时断点: tbreak
    5. gdb 设置显示选项 . 
    例如 set print pretty

    打印结构变量类型. ptype struct 类型名

    ----------------------------------------
    6.  让GDB记录输出信息到文件
    ----------------------------------------
    (gdb) set logging on    -> 打开记录功能。
    (gdb) set logging off -> 关闭记录功能。
    (gdb) show logging -> 显示记录功能中每个选项的设置。
    Future logs will be written to gdb.txt.   ---> 表示log 没有打开
    Currently logging to "gdb.txt".    --> 表示log 已经打开

    ----------------------------------------

    7. gdb 调试运行的进程

    gdb -p <进程号>

    或者:

    gdb

    attach <进程号>

    ----------------------------------------
    下面不常用的功能,供了解
    set logging file -> 改变记录文件,默认记录文件是gdb.txt。
    set logging overwrite [on|off] -> 默认这个项目是关闭,
    也就是以添加的形式将记录信息写入文件,这样之前文件中的信息不会被覆盖掉。
    注意:如果设置这个选项的时候记录功能已经打开,需要关闭记录功能再重新打开才能起作用。
    set logging redirect [on|off] -> 设置输出信息只记录到文件不作显示。
    默认这个项目是关闭的,GDB将输出信息到终端和记录文件。如果打开这个项目,GDB将只输出信息到记录文件。
    注意:如果设置这个选项的时候记录功能已经打开,需要关闭记录功能再重新打开才能起作用。
    注意:记录功能只能记录GDB的输出信息,被调试程序的输出信息仍然会输出到终端。

    ---------------------------------------
    7. 为什么没有core文件生成呢?

    ---------------------------------------
    有时候程序down了, 但是core文件却没有生成. 
    core文件的生成跟你当前系统的环境设置有关系, 可以用下面的语句设置一下, 然后再运行程序便生成了core文件.
    ulimit -c unlimited
    gdb a.out core
    ----------------------------------------
    8. gdb加载内核模块符号信息:
    add-symbol-file <module_file>ADDR

    例:

    root@ubuntu:/home/hjj/software/uxfs_now/kern# cat debug
    set remotebaud 115200
    target remote /dev/ttyS1
    add-symbol-file uxfs.ko 0xe0886000 -s .exit.text 0xe0888404
    -------------------------------------------------
    附录:gdb  常用调试命令
    -------------------------------------------------
    (gdb) l :(字母l)列出源码
    (gdb) b n :在第n行处设置断点
    (gdb) b func:在函数func()的入口处设置断点

    (gdb) 条件断点:条件可以是任何合法的c 表达式。 例如 b n if val1==val2

              当已经设置了断点,可以用condition 命令对断点号添加条件, 例: condition 2 val1==val2 , 注意,没有if 单词

              当对变量的改变更感兴趣时,可以用watch 命令

     (gdb) info break: 查看断点信息
    (gdb) r:运行程序
    (gdb) n:单步执行
    (gdb) s:单步调试如果有函数调用,则进入函数;与命令n不同,n是不进入调用的函数的
    (gdb) c:继续运行
    (gdb) p 变量 :打印变量的值     也能够修改变量的值(用 = 赋值) // 打印寄存器值。 p $eax

    (gdb) x/nfu 显示内存  // n为个数,f 为格式,u为每单元长度
    (gdb) bt:查看函数堆栈
    (gdb) finish:退出函数

    (gdb) display 每次中断或单步都显示你关心的变量

    (gdb)undisplay <编号>
    (gdb) shell 命令行:执行shell命令行
    (gdb) set args 参数:指定运行时的参数
    (gdb) show args:查看设置好的参数
    (gdb)info program: 来查看程序的是否在运行,进程号,被暂停的原因。 // 打印寄存器数组, info reg,  简写 i reg
    (gdb)clear 行号n:清除第n行的断点
    (gdb)delete 断点号n:删除第n个断点
    (gdb)disable 断点号n:暂停第n个断点
    (gdb)enable 断点号n:开启第n个断点

    ----------------------------------------
    gdb 条件调试
    ----------------------------------------
    “break ... if cond" 命令
    例: b fun() if i==20
    ignore bnum count
    watch var    // 此时监测var的变化
    ----------------------------------------
    gdb 多线程调试
    ----------------------------------------
    info threads
    thread ID 来切换
    set scheduler-locking off|on|step 估计实际使用过多线程调试的人都可以发现,
    在使用step或者continue命令调试当前被调试线程的时候,其他线程也是同时执行的,
    怎么只让被调试程序执行呢?通过这个命令就可以实现这个需求。
    off 不锁定任何线程,也就是所有线程都执行,这是默认值。 
    on 只有当前被调试程序会执行。 
    设置 on 之后, step 在单步的时候, 只有当前线程会执行。
    next过一个函数的情况(熟悉情况的人可能知道,这其实是一个设置断点然后continue的行为)。

    1.gdb有个很友好的调式界面,就是layout方式调试,可以让你边调试变查看源码,
    但是layout方式有时候有点bug,不过基本上还可以使用,
    可以用ctrl+x和ctrl+a 来切换。可能layout会造成控制台花屏,使用ctrl+L清屏。
    ----------------------------------------
     GDB堆栈跟踪的方法
    ----------------------------------------
    程序“调用堆栈”是当前函数之前的所有已调用函数的列表(包括当前函数)。每个函数及其变量都被分配了一个“帧”,
    最近调用的函数在 0 号帧中(“底部”帧)。要打印堆栈,发出命令 'bt'('backtrace' [回溯] 的缩写):
    (gdb) bt
    #0  0x80483ea in wib (no1=8, no2=8) at eg1.c:7
    #1  0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21
    此结果显示了在 main() 的第 21 行中调用了函数 wib()(只要使用 'list 21' 就能证实这一点),而且 wib() 在 0 号帧中,
    main() 在 1 号帧中。由于 wib() 在 0 号帧中,那么它就是执行程序时发生算术错误的函数。
    实际上,发出 'info locals' 命令时,gdb 会打印出当前帧中的局部变量,缺省情况下,这个帧中的函数就是被中断的函数(0 号帧)。
    可以使用命令 'frame' 打印当前帧。要查看 main 函数(在 1 号帧中)中的变量,可以发出 'frame 1' 切换到 1 号帧,
    然后发出 'info locals' 命令:
    (gdb) frame 1
    #1  0x8048435 in main (argc=1, argv=0xbffff9c4) at eg1.c:21
    21          result = wib(value, div);
    (gdb) info locals
    value = 8
    div = 8
    result = 4
    i = 2
    total = 6
    此信息显示了在第三次执行 "for" 循环时(i 等于 2)发生了错误,此时 "value" 等于 "div"。
    可以通过如上所示在 'frame' 命令中明确指定号码,或者使用 'up' 命令在堆栈中上移以及 'down' 命令在堆栈中下移来切换帧。
    要获取有关帧的进一步信息,如它的地址和程序语言,可以使用命令 'info frame'。
    gdb 堆栈命令可以在程序执行期间使用,也可以在 core 文件中使用,因此对于复杂的程序,可以在程序运行时跟踪它是如何转到函数的。
     查看栈信息
    当程序被停住了,你需要做的第一件事就是查看程序是在哪里停住的。 当你的程序调用了一个函数,
    函数的地址,函数参数,函数内的局部变量都会被压入“栈”(Stack)中。你可以用GDB命令来查看当前的栈中的信息。
    下面是一些查看函数调用栈信息的GDB命令:
        Backtrace,bt 打印当前的函数调用栈的所有信息。如:        
            (gdb) bt
            #0 func (n=250) at tst.c:6
            #1 0x08048524 in main (argc=1, argv=0xbffff674) at tst.c:30
            #2 0x400409ed in __libc_start_main () from /lib/libc.so.6        
    从上可以看出函数的调用栈信息:__libc_start_main --> main() --> func()    
        backtrace , bt n是一个正整数,表示只打印栈顶上n层的栈信息。
        backtrace <-n> ,bt <-n> -n表一个负整数,表示只打印栈底下n层的栈信息。        
    如果你要查看某一层的信息,你需要在切换当前的栈,一般来说,程序停止时,最顶层的栈就是当前栈,如果你要查看栈下面层的详细信息,首先要做的是切换当前栈。
    frame ,f n是一个从0开始的整数,是栈中的层编号。比如:frame 0,表示栈顶,frame 1,表示栈的第二层。
        up          表示向栈的上面移动n层,可以不打n,表示向上移动一层。
    down 表示向栈的下面移动n层,可以不打n,表示向下移动一层。
    上面的命令,都会打印出移动到的栈层的信息。如果你不想让其打出信息。你可以使用这三个命令:
        select-frame 对应于 frame 命令。
        up-silently 对应于 up 命令。
        down-silently 对应于 down 命令。
    查看当前栈层的信息,你可以用以下GDB命令:
    frame 或 f       会打印出这些信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。
    info frame,info f 这个命令会打印出更为详细的当前栈层的信息,只不过,大多数都是运行时的内内地址。比如:函数地址,调用函数的地址,被调用函数的地址,目前的函数是由什么样的程序语言写成的、函数参数地址及值、局部变量的地址等等。如:
           (gdb) info f
           Stack level 0, frame at 0xbffff5d4:
             eip = 0x804845d in func (tst.c:6); saved eip 0x8048524
             called by frame at 0xbffff60c
             source language c.
             Arglist at 0xbffff5d4, args: n=250
             Locals at 0xbffff5d4, Previous frame's sp is 0x0
             Saved registers:
             ebp at 0xbffff5d4, eip at 0xbffff5d8              
         info args      打印出当前函数的参数名及其值。     
         info locals     打印出当前函数中所有局部变量及其值。        
         info catch      打印出当前的函数中的异常处理信息。
    gdb 跟踪远程调试命令交互过程
    "set debug remote 1" 将显示gdb 远程调试的过程。

    gdb 内存断点设置。
    rwatch, watch, awatch 分别代表读,写,读写内存断点,用的是硬件断点。
    ------------------------------------------------------------------
    gdbtui  gdb 图形化接口。方便调试
    启动gdb, 按ctrl-x cta-a 也可进入tui 模式
    ------------------------------------------------------------------
     help layout
         layout src
         layout asm
         layout split
     help winheight
         winheight src +5
         winheight src -4
    help focus
        focus cmd
        focus src
        focus next
    简记为
        fs next
        fs src
        fs cmd
    --------------------------------------------------------------------------------
    help info
    Generic command for showing things about the program being debugged.
    关于被调试程序的一些信息
    help show
    Generic command for showing things about the debugger
    关于debugger状态的一些信息
    --------------------------------------------------------------------------------
    常用命令:

    set args

    file

    source

    set logging overwrite on
    set logging on
    set print pretty
    show logging
    set pagination off   // 不要出现 Type to continue 的提示信息 
    info functions
    info variables

    list

    list [函数名称]

    与调试控制相关的命令

    • continue    继续运行程序直到下一个断点(类似于VS里的F5)
    • next        逐过程步进,不会进入子函数(类似VS里的F10)
    • step        逐语句步进,会进入子函数(类似VS里的F11)
    • until        运行至当前语句块结束
    • finish  运行至函数结束并跳出,并打印函数的返回值(类似VS的Shift+F11)

    s : step in

    fin: step out, 跳出函数

    until 行号。 可用于跳出循环,加快了调试速度。

    ----------------------------------------
    gdb 调试跟踪多进程程序
    ----------------------------------------
    gdb只能跟踪一个进程(默认是跟踪父进程),而不能同时跟踪多个进程,
    可以设置gdb跟踪父进程还是子进程, 命令如下:
    set follow-fork-mode parent 跟踪父进程, 默认
    set follow-fork-mode child  跟踪子进程
    转自:http://blog.csdn.net/hejinjing_tom_com/article/details/9472041

  • 相关阅读:
    deploy.sh
    error cross-zip@4.0.0: The engine "node" is incompatible with this module. Expected version ">=12.10". Got "10.16.3"
    could not compile E:\sqjw-web\node_modules\@nuxt\vue-app\template\App.js:connot resolve "element-ui/lib/theme-chalk/index.css"
    js如何查看元素类型
    修改tomcat浏览器地址栏图标
    a different object with the same identifier value was already associated withthe session异常解决方案
    org.springframework.dao.InvalidDataAccessApiUsageException:The given object has a null identifi的解决方案
    利用JS验证查询参数-选择月份后必须选择年份
    算术运算函数
    数据的输出与数值传递
  • 原文地址:https://www.cnblogs.com/oxspirt/p/12469930.html
Copyright © 2011-2022 走看看