JavaScript, 250249 bytes
P=>{for(Q=[...P],P.M=[],Q.M=[],I=J=Q.p=1;E=I+J&&P[P.$??=0];[P,Q]=[Q,P])with(P)I=J,J=1,([[K[[,O],X,Y]=E.split` `).map(C=>P[C]??=0),eval({g:X+`>0&&($+=${Y}-1)`,n:`Q.M.push(${X})`,c:`M+""?${X}=M.shift():J=0`}[O]||X+{e:'=',d:'+=',u:'*=',o:'%='}[O]+Y),$+=J}
F=P=>{
for(
// Initial
Q=[...P], // P is process 0; Q is process 1
// We use the array for instructions,
// use P.a ~ P.z for registers
// use P._ for message queue received
// use P.$ for index of next instruction to execute
P._=[], // initial message queue
Q._=[],
I=J= // is last instruction successfully executed? (not waiting message)
Q.p=1; // initial Process ID for Process Q
// While not dead lock and not out of range
E=I+J&& // terminate if all process waiting message
P[P.$??=0]; // next instruction to execute
// Swap process schedule after each instruction execution
[P,Q]=[Q,P]
)with(P) // use variables in process P
I=J,J=1, // initial success flags
([
[K[,O], // The second character is different for each instruction
X, Y // two operand
]=E.split` `) // parse instruction, split it by space
.map(C=>P[C]??=0), // Initial any value used to 0
eval({ // execute the instruction
g:X+`>0&&($+=${Y}-1)`, // jgz
n:`Q._.push(${X})`, // snd
c:`_+""?${X}=_.shift():J=0` // rcv
}[O]||
X+{e:'=',d:'+=',u:'*=',o:'%='}[O]+Y // other math op
),
$+=J // move to next instruction if success
}
F=P=>{for(Q=[...P],P.M=[],Q.M=[],I=J=Q.p=1;E=I+J&&P[P.$??=0];[P,Q]=[Q,P])with(P)I=J,J=1,([[K[[,O],X,Y]=E.split` `).map(C=>P[C]??=0),eval({g:X+`>0&&($+=${Y}-1)`,n:`Q.M.push(${X})`,c:`M+""?${X}=M.shift():J=0`}[O]||X+{e:'=',d:'+=',u:'*=',o:'%='}[O]+Y),$+=J}
<form onsubmit="F(T.value.trim().split(/\s*[\n;]\s*/));return false;">
<textarea id=T></textarea>
<br>
<button id=R>RUN</button>
</form>
-1 byte by Neil