(操作系统)如何在c中使用asm mfence

(操作系统)如何在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和这里搜索这个问题,但我找不到来源 我不知道这是一个愚蠢的问题,因为我不是主修计算机科学的 另外,我想知道为什么这个代码

我在上一堂操作系统课,我的教授给了我们这个家庭作业

“将asm mfence放置在适当的位置。”

这个问题是关于使用多线程及其副作用的

主线程正在增加shared_var,但线程1正在同时增加它

因此,当代码增加2000000倍时,shared_var变为199048359.000

教授说u asm mfence将解决这个问题。但是,我不知道放在哪里

我试图在谷歌、github和这里搜索这个问题,但我找不到来源

我不知道这是一个愚蠢的问题,因为我不是主修计算机科学的

另外,我想知道为什么这个代码显示199948358.0000而不是2000000.00

任何帮助都将不胜感激

#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之后