//进程0 while(True){ while(turn != 0); //等待turn等于0 critical_region(); turn = 1; //离开临界区 noncritical_region(); } //进程1 while(True){ while(turn != 1); //等待turn等于1 critical_region(); turn = 0; //离开临界区 noncritical_region(); }
#define N 2 //进程数量 int turn; //锁变量 int interested[N]; void enter_region(int process){ int other; other = 1 - process; //其他进程 interested[process] = True; turn = process; while(turn==process && interested[other]==True); //等待other离开临界区 } void leave_region(int process){ interested[process] = False; }
//用TSL指令进入和离开临界区 enter_region: TSL REGISTER, LOCK ;复制锁变量到寄存器,并将锁设为1 CMP REGISTER, #0 JNE enter_region ;若锁不等于0,继续循环等待 RET leave_region: MOVE LOCK, #0 RET
//用XCHG指令进入和离开临界区 enter_region: MOVE REGISTER, #1 XCHG REGISTER, LOCK ;交换寄存器与锁变量的内容 CMP REGISTER, #0 ;测试锁 JNE enter_region ;若锁不等于0,继续循环等待 RET leave_region: MOVE LOCK, #0 RET
#define N 100 //缓冲区大小 int count = 0; //数据生产者 void producer(void){ int item; while(True){ item = produce_item(); //产生下一新数据项 if(count==N)sleep(); //如果缓冲区满,就进入休眠状态 insert_item(item); //将新数据项放入缓冲区 count++; if(count==1)wakeup(consumer);//唤醒消费者 } } //数据消费者 void consumer(void){ int item; while(True){ if(count==0)sleep(); //缓冲区为空,就进入休眠状态 item = remove_item(); //从缓冲区取走一个数据项 count--; if(count==N-1)wakeup(producer);//唤醒生产者 consume_item(item); } }
信号量是设置一个整型变量来累计唤醒次数。对信号量有两种操作:down和up(一般化后的sleep和wankeup)。
对信号量执行down操作,则是先检查其值是否大于0,若该值大于0,则将其值减1并继续,若该值为0,则进程将睡眠。这里,检查数值、修改变量值以及可能发生的睡眠操作是一个原子操作(不会被中间打断)。
up操作对信号量加1,信号量的增值1和唤醒操作同样是不可分割的。
#define N 100 //缓冲区大小 typedef int semaphore; semaphore full = 0; //缓冲区已用数目 semaphore empty = N; //缓冲区剩余数目 semaphore mutex = 1; //控制对临界区的访问 //数据生产者 void producer(void){ int item; while(True){ item = produce_item(); //产生下一新数据项 down(&empty); down(&mutex); //进入临界区 insert_item(item); up(&mutex); //离开临界区 up(&full); } } //数据消费者 void consumer(void){ int item; while(True){ down(&full); down(&mutex); item = remove_item(); up(&mutex); up(&empty); consume_item(item); } }
代码说明:mutex是一个二元信号量,每个进程在进入临界区前都对它执行一个down操作,离开临界区后执行一个up操作,就能够实现互斥。信号量的另一个作用是实现同步(synchronization),信号量full和empty用来保证当缓冲区满地时候生产者停止运行,以及当缓冲区空的时候消费者停止运行。
互斥量是一种退化的信号量,它只有两种状态:加锁和解锁,这样只要一个二进制位即可表示。
互斥量在实现用户级线程包时非常有用。当一个线程需要访问临界区时,调用mutex_lock,如果该互斥量当前是解锁的,此调用成功,调用线程可以自由进入临界区。如果该互斥量已经加锁,调用线程被阻塞,直到在临界区中的线程完成并调用mutex_unlock。如果多个线程被阻塞在该互斥量上,将随机选择一个线程并允许它获得锁。
mutex_lock: TSL REGISTER, MUTEX CMP REGISTER, #0 ;测试互斥量 JZE ok ;解锁 CALL thread_yield ;互斥量忙,调度另一个线程 JMP mutex_lock ;稍后再试 ok: RET mutex_unlock: MOVE MUTEX, #0 RET
pthread_mutex_init |
创建一个互斥量 |
pthread_mutex_destroy |
撤销一个已经存在的互斥量 |
pthread_mutex_lock |
获得一个锁或阻塞 |
pthread_mutex_unlock |
解锁 |
pthread_mutex_trylock |
获得一个锁或失败 |
pthread_cond_init |
创建一个条件变量 |
pthread_cond_destroy |
撤销一个条件变量 |
pthread_cond_wait |
阻塞调用线程直到另一个线程给它发信号 |
pthread_cond_signal |
向另一个线程发信号来唤醒它 |
pthread_cond_broadcast |
向多个线程发信号来让它们全部唤醒 |
消息传递使用的两条原语:send和receive,前一个调用向目标发送一条消息,后一个调用从一个给定的源接收一条消息。如果没有消息可用,则接收者可能被阻塞,直到下一条消息到达,或者带着一个错误码立即返回。
消息传递过程中可能发生消息丢失的现象,因此,一旦接收到消息,接收方应该回送一条确认消息(acknowledge),如果发送方在一段时间间隔内没有收到确认,则重发消息。而如果消息本身被正确接收,但返回给发送方的确认消息丢失,发送者将重发消息,这样将导致接收者接收到两次相同的消息。通常采用在每条原始消息中嵌入一个连续的序号来解决此问题,如果接收者接收到一条消息,并且它具有与前面某条消息一样的序号,就知道这条消息是重复的。
#define N 100 void producer(void){ int item; message m; while(True){ item = produce_item(); receive(consumer, &m); //等待消费者发送空缓冲区 build_message(&m, item);//建立一个待发送的信号 send(consumer, &m); //发送数据项给消费者 } } void consumer(void){ int item; message m; for(int i=0; i<N; ++i) send(producer, &m); //发送N个空缓冲区 while(True){ receive(produce, &m); //接收包含数据项的消息 item = extract_item(&m);//将数据项从消息中提取出来 send(producer, &m); //将空缓冲区发送回生产者 consume_item(item); } }