先决条件——临界区、进程同步、进程间通信
Bakery 算法是 N 过程一般情况下互斥问题的最简单的已知解决方案之一。 Bakery 算法是 N 个过程的关键部分解决方案。该算法保留了先到先得的特性。
- 在进入其临界区之前,该进程会收到一个数字。最小编号的持有者进入临界区。
- 如果进程 Pi 和 Pj 收到相同的数字,
if i < j Pi is served first; else Pj is served first.
- 编号方案总是以枚举的递增顺序生成数字;即 1, 2, 3, 3, 3, 3, 4, 5, …
符号 –字典顺序(票号,进程 ID #) – 首先比较票号。如果相同,则接下来比较进程 ID,即-
– (a, b) < (c, d) if a < c or if a = c and b < d
– max(a [0], . . ., a [n-1]) is a number, k, such that k >= a[i] for i = 0, . . ., n - 1
共享数据——选择的是一个布尔值数组 [0..n – 1]; & number 是一个包含整数值的数组 [0..n – 1]。两者都分别初始化为False 和 Zero 。
算法伪代码 –
repeat
choosing[i] := true;
number[i] := max(number[0], number[1], ..., number[n - 1])+1;
choosing[i] := false;
for j := 0 to n - 1
do begin
while choosing[j] do no-op;
while number[j] != 0
and (number[j], j) < (number[i], i) do no-op;
end;
critical section
number[i] := 0;
remainder section
until false;
解释 –
首先,进程将其“选择”变量设置为 TRUE,表明其意图进入临界区。然后它被分配与其他进程对应的最高票号。然后将“choosing”变量设置为 FALSE 表示它现在有一个新的票号。这实际上是算法中最重要和最令人困惑的部分。
它本身就是一个小的临界区!前三行的真正目的是,如果一个进程正在修改其 TICKET 值,那么此时不应允许其他进程检查其现在已过时的旧票证值。这就是为什么在检查票证值之前的 for 循环中,我们首先确保所有其他进程的“选择”变量为 FALSE。
之后,我们继续检查具有最少票号/进程 ID 的进程进入临界区的进程的票证值。退出部分只是将票证值重置为零。
代码——这是烘焙算法的 C 代码实现。在 UNIX 环境中运行以下命令 –
// Importing the thread library
#include "pthread.h"
#include "stdio.h"
// Importing POSIX Operating System API library
#include "unistd.h"
#include "string.h"
// This is a memory barrier instruction.
// Causes compiler to enforce an ordering
// constraint on memory operations.
// This means that operations issued prior
// to the barrier will be performed
// before operations issued after the barrier.
#define MEMBAR __sync_synchronize()
#define THREAD_COUNT 8
volatile int tickets[THREAD_COUNT];
volatile int choosing[THREAD_COUNT];
// VOLATILE used to prevent the compiler
// from applying any optimizations.
volatile int resource;
void lock(int thread)
{
// Before getting the ticket number
//"choosing" variable is set to be true
choosing[thread] = 1;
MEMBAR;
// Memory barrier applied
int max_ticket = 0;
// Finding Maximum ticket value among current threads
for (int i = 0; i < THREAD_COUNT; ++i) {
int ticket = tickets[i];
max_ticket = ticket > max_ticket ? ticket : max_ticket;
}
// Allotting a new ticket value as MAXIMUM + 1
tickets[thread] = max_ticket + 1;
MEMBAR;
choosing[thread] = 0;
MEMBAR;
// The ENTRY Section starts from here
for (int other = 0; other < THREAD_COUNT; ++other) {
// Applying the bakery algorithm conditions
while (choosing[other]) {
}
MEMBAR;
while (tickets[other] != 0 && (tickets[other]
< tickets[thread]
|| (tickets[other]
== tickets[thread]
&& other < thread))) {
}
}
}
// EXIT Section
void unlock(int thread)
{
MEMBAR;
tickets[thread] = 0;
}
// The CRITICAL Section
void use_resource(int thread)
{
if (resource != 0) {
printf("Resource was acquired by %d, but is still in-use by %d!\n",
thread, resource);
}
resource = thread;
printf("%d using resource...\n", thread);
MEMBAR;
sleep(2);
resource = 0;
}
// A simplified function to show the implementation
void* thread_body(void* arg)
{
long thread = (long)arg;
lock(thread);
use_resource(thread);
unlock(thread);
return NULL;
}
int main(int argc, char** argv)
{
memset((void*)tickets, 0, sizeof(tickets));
memset((void*)choosing, 0, sizeof(choosing));
resource = 0;
// Declaring the thread variables
pthread_t threads[THREAD_COUNT];
for (int i = 0; i < THREAD_COUNT; ++i) {
// Creating a new thread with the function
//"thread_body" as its thread routine
pthread_create(&threads[i], NULL, &thread_body, (void*)((long)i));
}
for (int i = 0; i < THREAD_COUNT; ++i) {
// Reaping the resources used by
// all threads once their task is completed !
pthread_join(threads[i], NULL);
}
return 0;
}
输出: