Peterson算法是一个实现互斥锁的并发程序设计算法,可以控制两个进程访问一个共享的单用户资源而不发生访问冲突。
- 假设编译器和硬件保证了顺序和可见性
- 实现一段任意长代码的原子性
void f() {
int i;
for(i = 0; i < 1000000; i++)
{
lock(); // 保证顺序、原子性、可见性
x++; // 临界区
unlock();
}
}
而Peteson算法的实现如下:
int turn, x = 0, y = 0;
void thread1()
{
x = 1; turn = T2;
while (y && turn == T2) ;
// critical section
x = 0;
}
void thread2()
{
y = 1; turn = T1;
while (x && turn == T1) ;
// critical section
y = 0;
}
例如,求和:
#include <threads.h>
int sum = 0;
int turn, x = 0, y = 0;
const int T1 = 0;
const int T2 = 1;
const int MAXN = 1e7;
void thread1()
{
int i;
for(i = 0; i < MAXN; i++)
{
x = 1; turn = T2;
while (y && turn == T2) ;
// critical section
sum++;
//
x = 0;
}
}
void thread2()
{
int i;
for(i = 0; i < MAXN; i++)
{
y = 1; turn = T1;
while (x && turn == T1) ;
// critical section
sum++;
//
y = 0;
}
}
void cal()
{
printf("sum = %d\n", sum);
}
int main()
{
create(thread1);
create(thread2);
join(cal);
}
实验结果如下: 【使用Perterson】
【不使用Perterson】
显然,虽然Perterson算法在实际机器上无法实现真正的互斥(因为实际机器无法保证顺序和可见性),但效果要远好于不加互斥算法的原始程序。
当然,我们可以使用内存屏障来保证顺序性和可见性:
mfence, lfence, sfence什么作用?
#define mb() asm volatile("mfence":::"memory")
#define rmb() asm volatile("lfence":::"memory")
#define wmb() asm volatile("sfence" ::: "memory")
就是保证内存访问的串行化,内部操作就是在一系列内存访问中添加若干延迟,保证此指令之后的内存访问发生在此指令之前的内存访问完成之后(不出现重叠)。
lfence 读串行化
sfence 写串行化
mfence 读写都串行化
#include <threads.h>
#define FENCE asm volatile ("mfence")
int sum = 0;
int turn, x = 0, y = 0;
const int T1 = 0;
const int T2 = 1;
const int MAXN = 1e7;
void thread1()
{
int i;
for(i = 0; i < MAXN; i++)
{
x = 1; FENCE;
turn = T2; FENCE;
while (1)
{
if(!y) break; FENCE;
if(turn != T2) break; FENCE;
}
// critical section
sum++;
//
x = 0;
}
}
void thread2()
{
int i;
for(i = 0; i < MAXN; i++)
{
y = 1; FENCE;
turn = T1; FENCE;
while (1)
{
if(!x) break; FENCE;
if(turn != T1) break; FENCE;
};
// critical section
sum++;
//
y = 0;
}
}
void cal()
{
printf("sum = %d\n", sum);
}
int main()
{
create(thread1);
create(thread2);
join(cal);
}
结果如下: