• SV--线程(mailbox)


    3 mailbox

    3.1概述

    •    线程之间如果传递信息,可以使用mailbox。
    •    mailbox和队列queue有相近之处。

    •    mailbox是一个对象,因此也可以用new()来例化,例化时,有一个可选的参数size来限定其存储的最大数量,如果size是0或者没有指定,则信箱是无限大的,可以容纳任意多的条目。

    •    使用put()可以把数据放入mailbox, 使用get()可以从信箱移除数据。
    •    如果信箱为满,则put()会阻塞;如果信箱为空,则get()会阻塞。
    •    peek()可以获取对信箱里数据的拷贝而不移除它。
    •    线程之间的同步方法需要注意,哪些是阻塞方法,哪些是非阻塞 方法,即哪些是立即返回的,而哪些可能需要等待时间的。

    3.2代码演练 

    1. program automatic bounded;
    2. mailbox mbx;
    3. initial begin
    4. mbx = new(l); //容量为1
    5. fork
    6. // Producer线程
    7. for (int i=l; i<4; i++) begin
    8. $display("Producer: before put(%0d)", i);
    9. mbx.put(i);
    10. $display("Producer: after put(%0d}", i);
    11. end
    12. // Consumer线程
    13. repeat(4) begin
    14. int j;
    15. #lns mbx.get(j);
    16. $display("Consumer:after get(%0d}", j);
    17. end
    18. join
    19. end
    20. endprogram

    Producer: before put(1)
    Producer: after put(1)

    Producer: before put(2)

    Consumer: after get(1)

    Producer: after put(2)

    Producer: before put(3)

    Consumer: after get(2)

    Producer: after put (3)

    Consumer: after get(3) 

    3.3实例演练

    对于一辆车子而言,如果要实时显示这辆车子的状态,会需要多个仪表。显示的参数包括车速、油量、发动机的转速和温度等, 这些参数涉及到了各个传感器到汽车控制中枢的通信。

    如果我们继续通过上面这辆BYD, 来模拟不同传感器(线程)到车的中央显示的通信,可以利用SV的mailbox (信箱)来满足多个线程之间的数据通信。

    1. class car;
    2. mailbox tmp_mb, spd_mb, fuel_mb;
    3. int sample_period;
    4. function new();
    5. sample_period = 10;
    6. tmp_mb = new();
    7. spd_mb = new();
    8. fuel_mb = new();
    9. endfunction
    10. task sensor_tmp;
    11. int tmp;
    12. forever begin
    13. std::randomize(tmp) with {tmp >= 80 && tmp <= 100;};
    14. tmp_mb.put(tmp);
    15. #sample_period;
    16. end
    17. endtask
    18. task sensor_spd,
    19. int spd;
    20. forever begin
    21. std::randomize(spd) with {spd>= 50 && spd <=60;};
    22. spd_mb.put(spd),
    23. #sample_period;
    24. end
    25. endtask
    26. task sensor_fuel;
    27. int fuel;
    28. forever begin
    29. Std::randomize (fuel) with {fuel>= 30 && fuel <= 35;};
    30. fuel_mb.put(fuel);
    31. #sample_periode;
    32. end
    33. endtask
    34. task drive () ;
    35. fork
    36. sensor_tmp ();
    37. sensor_spd ();
    38. sensor_ fuel ();
    39. display(tmp_mb, "temperature");
    40. display(spd_mb, "speed");
    41. display(fuel_mb, "feul");
    42. join_none
    43. endtask
    44. task display(mailbox_mb, string name="mb") ;
    45. int val;
    46. forever begin
    47. mb.get(val):
    48. $display ("car: %s is %0d", name, val)
    49. end
    50. endtask
    51. endclass
    52. module road;
    53. car byd = new();
    54. initial begin
    55. byd. drive();
    56. end
    57. endmodule

     -    # car:: temperature is 100
    -    # car:: speed is 50
    -    # car:: feul is 30
    -    # car:: temperature is 96
    -    # car:: speed is 55 
    -    # car:: feul is 33
    -    # car:: temperature is 89
    -    # car:: speed is 57 
    -    # car:: feul is 31

    对于mailbox的用法,与FIFO的使用很相似。如果我们将上面的mailbox用队列来替代的话,则可以修改为下面的例码。 

    1. class car;
    2. //mailbox tmp_mb, spd_mb, fuel_mb;
    3. int tmp_q[$],spd_q[$],fuel_q[$];
    4. int sample_period;
    5. function new();
    6. sample_period = 10;
    7. //tmp_mb = new();
    8. //spd_mb = new();
    9. //fuel_mb = new();
    10. endfunction
    11. task sensor_tmp;
    12. int tmp;
    13. forever begin
    14. std::randomize(tmp) with {tmp >= 80 && tmp <= 100;};
    15. //tmp_mb.put(tmp);
    16. tmp_q.push_back(tmp);
    17. #sample_period;
    18. end
    19. endtask
    20. task sensor_spd,
    21. int spd;
    22. forever begin
    23. std::randomize(spd) with {spd>= 50 && spd <=60;};
    24. //spd_mb.put(spd);
    25. spd_q.push_back (spd);
    26. #sample_period;
    27. end
    28. endtask
    29. task sensor_fuel;
    30. int fuel;
    31. forever begin
    32. Std::randomize (fuel) with {fuel>= 30 && fuel <= 35;};
    33. //fuel_mb.put(fuel);
    34. fuel_q.push_back(fuel);
    35. #sample_periode
    36. end
    37. endtask
    38. task drive () ;
    39. fork
    40. sensor_tmp ();
    41. sensor_spd ();
    42. sensor_ fuel ();
    43. display(tmp_mb, "temperature");
    44. display(spd_mb, "speed");
    45. display(fuel_mb, "feul");
    46. join_none
    47. endtask
    48. task display(string name, ref int q[$]);
    49. int val;
    50. forever begin
    51. //mb.get(val);
    52. wait(q.size() > 0) ;
    53. val = q.pop_front();
    54. $display ("car: : %s is %0d", name,val);
    55. //$display ("car: %s is %0d", name, val);
    56. end
    57. endtask
    58. endclass
    59. module road;
    60. car byd = new();
    61. initial begin
    62. byd. drive();
    63. end
    64. endmodule

    通过队列改造来完成进程间通信的例子, 我们可以找出maibox与queue在使用时的差别:

    maibox必须通过new()例化,而队列只需要声明。

    mailbox可以将不同的数据类型同时存储,不过这么做是不建议的,对于队列来讲,它内部存储的元素类型必须一致。

    mailbox 的存取方式put()和get()是阻塞方式,即使用它们时,方法不一定会立即返回,而队列所对应的存取方式,push_back()和pop_front()方法是非阻塞的,会立即返回。因此在使用queue取数时,需要额外填写wait(queue.size()>0)才可以在其后对非空的queue做取数操作,此外也应该注意,如果要调用阻塞方法,那么只可以在task中调用,因为阻塞方法是耗时的,而调用非阻塞方法,例如,queue中的push_back()和pop_front()方法,则既可以在task又可以在function中。

    mailbox只能够用作FIFO,而queue除了按照FIFO使用,还有其它应用的方式例如LIFO(last in first out)

    对于mailbox变量的操作,在传递形式参数时,实际传递并拷贝的是mailbox的指针,而在第二个例子中的task display(),关于queue的形式参数声明是ref方向,因为如果采用默认的input方向,那么传递过程中发生是数组的拷贝,以至于方向内部对queue的操作并不会影响外部的queue本身,因此在传递数组时,大家应该考虑到,对数组做的是引用还是拷贝,进而考虑端口的声明方向。

    •    mailbox在例化时,通过new(N)的方式可以使其变为定长 (fixed length)容器。这样在负载到长度N以后,无法再对其写入。如果用new()的方式,则表示信箱容量不限大小。
    •    除了put()/get()/peek()这样的阻塞方法,用户也可以考虑使用try _put()/try _get()/try _peek()等非阻塞方法。
    •    如果要显式地限定mailbox中元素的类型,可以通过mailbox #(type = T)的方式来声明。例如上面的三个mailbox存储的是int, 则可以在声明时进一步限定其类型为mailbox #(int)。

    3.4 event semaphore mailbox对同一功能的实现代码比较 

    •    除了上面的三种常见需求, 有时候, 进程之间也需要同步。 这里我们来考虑, 如果要对这辆车熄火(stall)的话, 得先检查是否车挂挡在P (park) , 进而再将车钥匙拔出。
    •    我们可以将stall和park两个线程的同步视作, 先由stall发起同步请求, 再等待park线程完成并响应同步请求, 最后由stall线程继续其余的程序, 最终结束熄火的过程。
    •    我们不妨用之前掌握的SV三种进程通信的方式event、semaphore和mailbox来解决进程间的同步问题。

    event代码实现: 

    1. class car;
    2. event e_stall;
    3. event e_park;
    4. task stall;
    5. $display ( "car::stall started");
    6. #lns;
    7. -> e_stall;
    8. @e_park;
    9. $display ( "car::stall finished") ;
    10. endtask
    11. task park;
    12. @e_stall;
    13. $display ("car: : park started");
    14. #lns;
    15. -> e_park;
    16. $display("car::park finished");
    17. endtask
    18. task drive() ;
    19. fork
    20. this.stall();
    21. this.park();
    22. join_none
    23. endtask
    24. endclass

    semaphore 代码实现:

    1. class car;
    2. semaphore key;
    3. function new();
    4. key= new(0);
    5. endfunction
    6. task stall;
    7. $display("car::stall started");
    8. #lns;
    9. key.put();
    10. key.get();
    11. $display("car::stall finished");
    12. endtask
    13. task park;
    14. key.get() ;
    15. $display("car: :park started");
    16. #lns;
    17. key.put() ;
    18. $display("car: :park finished");
    19. endtask
    20. task drive();
    21. fork
    22. this.stall();
    23. this.park();
    24. join_none
    25. endtask
    26. endclass

    mailbox代码实现:

    1. task stall;
    2. int val = 0;
    3. $display ("car:: stall started");
    4. #lns;
    5. mb.put(val);
    6. mb.get(val);
    7. $display("car::stall finished");
    8. endtask
    9. task park;
    10. int val = 0;
    11. mb.get(val);
    12. $display ("car::park started");
    13. #lns;
    14. mb.put(val);
    15. $display("car::park finished");
    16. endtask
    17. task drive();
    18. fork
    19. this.stall();
    20. this.park();
    21. join_none
    22. endtask
    23. endclass

    •    上面三种用来实现线程A请求同步线程B, 线程B再晌应线程A的同步 方式输出结果保持一致。
    •    从这三段代码可以看出, 用来做线程同步的选择也有多种。

    •    如果要在同步(事件)的同时, 完成一些数据传输, 那么更合适的是mailbox, 因为它可以用来存储一些数据,而event和semaphore更偏向于小信息量的同步, 即不包含更多的数据信息。
    # car::stall started
    # car::park started 
    # car::park finished 
    # car::stall finished 

    event: 最小信息量的触发,即单一的通知功能。可以用来做事件的触发, 也可以多个event组合起来用来做线程之间的同步。

    semaphore: 共享资源的安全卫士。如果多线程间要对某一公共资源做访问,即可以使用这个要素。

    mailbox: 精小的SV原生FIFO。在线程之间做数据通信或者内部数 据缓存时可以考虑使用此元素。

     

  • 相关阅读:
    Serverless是简化的Kubernetes
    在安卓模拟器(mumu为例)上联调app并且用Charles抓包
    C++内存管理以及模板的引入
    centos 6.10 安装 perl 5.14
    【推导】线性变换与在基下的矩阵一一对应
    牛客周赛 Round 15 D 游游的树上边染红(树形dp)
    Go 语言 select 都能做什么?
    010_第一代软件开发(二)
    常用工具记录
    牛客: BM17 二分查找-I
  • 原文地址:https://blog.csdn.net/weixin_45680021/article/details/126078702