(操作系统)如何在c中使用asm mfence
我在上一堂操作系统课,我的教授给了我们这个家庭作业 “将asm mfence放置在适当的位置。” 这个问题是关于使用多线程及其副作用的 主线程正在增加shared_var,但线程1正在同时增加它 因此,当代码增加2000000倍时,shared_var变为199048359.000 教授说u asm mfence将解决这个问题。但是,我不知道放在哪里 我试图在谷歌、github和这里搜索这个问题,但我找不到来源 我不知道这是一个愚蠢的问题,因为我不是主修计算机科学的 另外,我想知道为什么这个代码显示199948358.0000而不是2000000.00 任何帮助都将不胜感激(操作系统)如何在c中使用asm mfence,c,winapi,memory,operating-system,barrier,C,Winapi,Memory,Operating System,Barrier,我在上一堂操作系统课,我的教授给了我们这个家庭作业 “将asm mfence放置在适当的位置。” 这个问题是关于使用多线程及其副作用的 主线程正在增加shared_var,但线程1正在同时增加它 因此,当代码增加2000000倍时,shared_var变为199048359.000 教授说u asm mfence将解决这个问题。但是,我不知道放在哪里 我试图在谷歌、github和这里搜索这个问题,但我找不到来源 我不知道这是一个愚蠢的问题,因为我不是主修计算机科学的 另外,我想知道为什么这个代码
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include <conio.h>
int turn;
int interested[2];
void EnterRegion(int process);
void LeaveRegion(int process);
DWORD WINAPI thread_func_1(LPVOID lpParam);
volatile double shared_var = 0.0;
volatile int job_complete[2] = {0, 0};
int main(void)
{
DWORD dwThreadId_1, dwThrdParam_1 = 1;
HANDLE hThread_1;
int i, j;
// Create Thread 1
hThread_1 = CreateThread(
NULL, // default security attributes
0, // use default stack size
thread_func_1, // thread function
&dwThrdParam_1, // argument to thread function
0, // use default creation flags
&dwThreadId_1
); // returns the thread identifier
// Check the return value for success.
if (hThread_1 == NULL)
{
printf("Thread 1 creation error\n");
exit(0);
}
else
{
CloseHandle( hThread_1 );
}
/* I am main thread */
/* Now Main Thread and Thread 1 runs concurrently */
for (i = 0; i < 10000; i++)
{
for (j = 0; j < 10000; j++)
{
EnterRegion(0);
shared_var++;
LeaveRegion(0);
}
}
printf("Main Thread completed\n");
job_complete[0] = 1;
while (job_complete[1] == 0) ;
printf("%f\n", shared_var);
_getch();
ExitProcess(0);
}
DWORD WINAPI thread_func_1(LPVOID lpParam)
{
int i, j;
for (i = 0; i < 10000; i++) {
for (j = 0; j < 10000; j++)
{
EnterRegion(1);
shared_var++;
LeaveRegion(1);
}
}
printf("Thread_1 completed\n");
job_complete[1] = 1;
ExitThread(0);
}
void EnterRegion(int process)
{
_asm mfence;
int other;
other = 1 - process;
interested[process] = TRUE;
turn = process;
while (turn == process && interested[other] == TRUE) {}
_asm mfence;
}
void LeaveRegion(int process)
{
_asm mfence;
interested[process] = FALSE;
_asm mfence;
}
#包括
#包括
#包括
#包括
内翻;
有兴趣的[2];
无效区域(int过程);
空区(int进程);
DWORD WINAPI线程函数1(LPVOID lpParam);
易失性双共享_var=0.0;
volatile int job_complete[2]={0,0};
内部主(空)
{
DWORD dwThreadId_1,dwThrdParam_1=1;
句柄hThread_1;
int i,j;
//创建线程1
hThread_1=创建线程(
NULL,//默认安全属性
0,//使用默认堆栈大小
thread_func_1,//线程函数
&dwThrdParam_1,//线程函数的参数
0,//使用默认创建标志
&dwid_1
);//返回线程标识符
//检查返回值是否成功。
if(hThread_1==NULL)
{
printf(“线程1创建错误\n”);
出口(0);
}
其他的
{
CloseHandle(hThread_1);
}
/*我是主线*/
/*现在主线程和线程1同时运行*/
对于(i=0;i<10000;i++)
{
对于(j=0;j<10000;j++)
{
肠区(0);
共享_var++;
离开区域(0);
}
}
printf(“主线程已完成\n”);
作业完成[0]=1;
while(作业完成[1]==0);
printf(“%f\n”,共享变量);
_getch();
退出过程(0);
}
DWORD WINAPI线程函数1(LPVOID lpParam)
{
int i,j;
对于(i=0;i<10000;i++){
对于(j=0;j<10000;j++)
{
肠区(1);
共享_var++;
区域(1);
}
}
printf(“线程1已完成\n”);
作业完成[1]=1;
退出线程(0);
}
无效区域(int进程)
{
_asm-mfence;
int其他;
其他=1-过程;
感兴趣的[过程]=真;
回合=进程;
while(turn==process&&interest[other]==TRUE){}
_asm-mfence;
}
无效区域(int进程)
{
_asm-mfence;
感兴趣的[过程]=假;
_asm-mfence;
}
EnterRegion()和LeaveRegion()函数正在使用一种称为“彼得森算法”的方法实现一个关键区域
现在,彼得森算法的关键是,当线程读取turn
时,它必须获取任何线程写入的最新(最新)值。也就是说,turn
上的操作必须顺序一致。另外,在entereregion()
中写入感兴趣的[]
必须在写入之前(或同时)对所有线程可见
因此,放置mfence
的位置在turn=过程之后代码>--这样线程在所有其他线程都可以看到它对的写入之前不会继续
说服编译器每次读取turn
和interest[]
时都从内存中读取也是很重要的,因此您应该将它们设置为volatile
如果您是为x86或x86_64编写这篇文章,这就足够了——因为它们通常“表现良好”,因此:
- 所有写入
turn
和感兴趣的[process]
的操作都将按程序顺序进行
- 对
turn
和interest[other]
的所有读取也将按程序顺序进行
设置这些volatile
也可以确保编译器不会篡改顺序
在这种情况下,在x86和x86_64上使用mfence
的原因是在继续读取turn
值之前将写入队列刷新到内存。因此,所有的内存写入都进入一个队列,在将来的某个时候,每次写入都将到达实际内存,写入的效果将对其他线程可见——写入已“完成”。以与程序相同的顺序写入“完成”,但延迟。如果线程读取最近写入的内容,处理器将从写入队列中选取(最近)的值。这意味着线程不需要等到写入“完成”,这通常是一件好事。但是,这确实意味着线程读取的值与任何其他线程读取的值不同,至少在写入“完成”之前是如此。mfence
所做的是暂停处理器,直到所有未完成的写入都“完成”——这样,任何后续读取都将读取与任何其他线程相同的内容
在LeaveRegion()
中写入感兴趣的[]
(在x86/x86_64上)不需要mfence
,这是一个好的操作,因为mfence
是一个代价高昂的操作。每个线程只会写入自己的感兴趣的[]
标志,并且只会读取其他线程。此写入的唯一限制是写入entereregion()
(!)后不能“完成”。令人高兴的是,x86/x86_64按顺序执行所有写入操作。[当然,在写入LeaveRegio之后