第三章:并发进程&进程同步

硬件设计可以看成是由很多并发或者并行的进程组成,在验证平台中,每一个逻辑独立的并行操作都是采用独立的进程来实现的。SV提供了如下处理并发进程的能力:

  1. fork...join并发结构
  2. 通过mailbox实现进程间的通信
  3. 通过semaphore实现进程互斥或仲裁
  4. 通过event实现进程间的同步

fork...join:

  

fork...join代码称为父进程,fork...join内的语句块称为子进程,子进程并行执行

并发方式:

在fork...join中,父进程会被阻塞,知道所有子进程结束,

 

在fork...join_any中,父进程会被阻塞到任意一个子进程结束,

 

在fork...join_none中父进程会立即与产生的所有进程并发执行,

 

进程与变量:默认情况下,子进程和父进程都可以修改父进程定义的变量,这类共享变化可以用作进程之间的通信,但也会导致进程之间的竞争。

例如:

该代码启动了十个并行进程,for循环为父进程,fork...join为子进程。SV中,会先执行循环体,而fork会先将子进程添加到进程管理器中,暂不执行,当父进程把循环体执行完,进程管理器中就有了十个并发进程,此时再并发的执行子进程,由于父进程已执行完毕,此时id=10,因此,所有并发进程的id均为10.

造成这种结果的原因是:id默认为静态的共享变量,在父进程执行完后,并发子进程共享同一个id。因此将id的类型设置为自动类型automattic,即拥有自动变量的子进程,在被fork调用时,一个新的id会被创建并赋值给子进程。

进程控制

wait fork:等待进程结束

disable/disable fork:终止进程执行。

wait fork用来确保所有子进程的执行都已经结束。

disable fork 与disable的不同在于:disable 会终止执行一个特性块的所有进程,无论进程是否由fork产生分支,而diaable fork仅仅终止那些由fork调用产生的进程。

mailbox:

mailbox的基本操作

进程间的通信方式除了变量,还可以使用mailbox。mailbox是一个队列,也可以看作是一个先进先出的存储数组(FIFO),他的创建方法:

new()//创建

原型:function new(int bound=0);bound表示队列长度,缺省值为0,即队列无限长,bound的值是非负值,用new方法创建的mailbox会返回一个mailbox句柄,如果创建不成功,则返回null

对mailbox的基本操作有三种:写入(put,try_put)、取出(get,try_get)、复制(peek,try_peek),其中带try前缀的为非阻塞操作。

Put()//存入

原型:task put (singular message);按FIFO的方式向mailbox中存储数据,如果mailbox是一个有界队列且队列已满,则put进程会被阻塞,直到队列有足够空间。

try_put()//存入反馈

原型:function int try_put(singular message);按FIFO的方式向mailbox中存储数据,tyr_put仅对有界mailbox有效,若mailbox没有满,则向mailbox中存入数据是返回1,若满了,则返回0

get()//取出并删除

原型:task get(ref singular message);该方法将队列中的数取出并删除,若mailbox为空,则get进程阻塞,知道有数据被放置到mailbox中,若数据变量与mailbox中的数据类型不匹配,则报错。

try_get()//取出删除反馈

原型:function int try_get(ref singular message);该方法将队列中的数取出并删除,若mailbox为空,则返回0,若数据类型不匹配,则返回负数,若mailbox不为空,即数据有效,且类型匹配,则返回1

peek()//复制出一个数据

原型:task peek(ref singular message);该方法复制mailbox中的一个数据,但不将其从队列中删除,若mailbox为空,则进程会被阻塞,知道有数据被放到mailbox中,若数据类型不匹配,则运行时报错。

try_peek()//复制出一个数据并反馈

原型:funcyion int try_peek(ref singular message);功能同peek一样,但当mailbox为空时,返回0。数据类型不匹配时返回负数。其他情况返回1。

num()

原型:function int num();该函数可以获得mailbox中存储数据的数目。

参数化mailbox

mailbox的缺省值是无类型的,也就是说,mailbox可以发送和接受任何类型的数据。但容易导致传输数据类型不匹配。所以需要对mailbox进行参数化声明,使其只能传输某种特定类型的数据。

声明并创建参数化mailbox变量:mailbox #(date_type) mailbox_name=new();

参数化mailbox与mailbox的创建一样都是使用new(),且取出,存入,复制也和mailbox一样。

typedef mailbox #(string) s_mb;

S_mb mb=new();

mb.put(“hello”);//将字符串”hello”存入名为mb的mailbox中

mb.get(s);//将名为mb的mailbox中的数据取出放入s中。

使用参数化的mailbox可以确保操作的数据类型与mailbox中的数据类型匹配,有错误时在编译时就可以被发现。

semaphore

多个进程通信时会出现竞争共享资源,所以必须通过仲裁来解决,也就是“互斥接入”。

semaphore是一个内建的类,相当于存储桶,当使用semaphore的进程执行前,需要向存储桶申请一个key,进程结束后,再将key放回存储桶中,如果申请不到key,则进程被阻塞。

semaphore的基本操作

创建一个semaphore例子:semaphore smTx;

semaphore的创建与mailbox相同,都是使用new(),都有基本的写入、取出、复制操作。

new()://分配semaphore存储桶的key数量

原型:function new(int keycount=0);keycount指定了被分配到semaphore存储桶中的key的数目,缺省值为0,该函数返回semaphore的句柄,如果没有产生semaphore,则返回null。

所以创建并声明一个semaphore类:semaphore sp_name=new();

put()://进程放回存储桶key数量

原型:task put(int keycount=1);keycount指定了放回存储桶key的数目,缺省值为1,若该semaphore进程还没有足够的key放回存储桶,则该进程被阻塞,直到有足够多的key放回存储桶。

get()://进程取走存储桶中key数量

原型:task get(int keycount=1);keycount指定了需要从semaphore存储桶中获得的key的数目,缺省值为1,若指定数目的key存在,则该语句返回,且程序继续执行,否则程序被阻塞。

try_get()://无阻塞取走存储桶中key数量

该方法用来无阻塞的获得指定数目的key,缺省值为1,如果指定数目的key存在,则返回1,否则返回0

事件event:

事件用来实现进程间的同步,事件的操作有:等待、触发

事件命名:event event_name;

事件触发

命名事件可以通过->操作符触发:event event_identifier;   -> event_identifier;

非阻塞事件使用->操作符触发。

等待事件

等待事件使用事件控制操作符@,或者使用wait()结构来检查事件的触发状态

等待事件会阻塞进程,直到事件被触发,因此等待进程要比触发事件先执行,如果触发先执行,则等待事件会被一直阻塞。

事件的触发属性

事件的triggered允许用户检查事件的触发状态,若事件已被触发,则triggered属性为真,否则为null

该属性常和wait()搭配使用:wait(enent_identifier.triggered);使用该种机制可以消除触发事件和等待事件的竞争。无论等待和触发谁先执行,总有一个可以接触进程阻塞。

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
读者与写者问题是多进程并发编程中的经典问题,主要解决的是多个进程同时对共享数据进行读写操作时可能产生的数据不一致性问题。具体来说,该问题假设有若干个读者和一个写者进程,它们同时访问某个共享资源(如一个文件、一个数据库等),读者只读取共享资源而不修改它,而写者则修改共享资源。 由于读者和写者对共享资源的访问方式不同,因此需要对它们的访问进行调度和同步,以防止读写冲突导致数据不一致。下面是一种实现读者与写者问题的方法: ```python from threading import Lock class ReaderWriter: def __init__(self): self.read_count = 0 self.mutex = Lock() # 互斥锁,用于保护共享资源的访问 self.write_lock = Lock() # 写锁,用于保证写操作的原子性 def start_read(self): self.mutex.acquire() self.read_count += 1 if self.read_count == 1: self.write_lock.acquire() # 第一个读者获取写锁,防止写者进程修改共享资源 self.mutex.release() def end_read(self): self.mutex.acquire() self.read_count -= 1 if self.read_count == 0: self.write_lock.release() # 最后一个读者释放写锁,允许写者进程修改共享资源 self.mutex.release() def start_write(self): self.write_lock.acquire() # 写锁保证写操作的原子性 def end_write(self): self.write_lock.release() # 释放写锁 ``` 在上述代码中,`ReaderWriter` 类实现了读者与写者的同步和调度。具体来说,它定义了三个方法: - `start_read`:读者开始读取共享资源时调用该方法,它首先获取互斥锁,然后增加读者计数器,如果是第一个读者,则获取写锁,以防止写者进程修改共享资源。 - `end_read`:读者读取完共享资源时调用该方法,它首先获取互斥锁,然后减少读者计数器,如果是最后一个读者,则释放写锁,允许写者进程修改共享资源。 - `start_write`:写者开始修改共享资源时调用该方法,它直接获取写锁,保证写操作的原子性。 - `end_write`:写者修改完共享资源时调用该方法,它释放写锁。 在实际使用中,可以将共享资源作为 `ReaderWriter` 类的一个属性,并在读者和写者进程中调用相应的方法,以实现进程同步和数据一致性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值