P
Pushkar Prasad
Hello Everybody
What is the best way to ensure atomic update to a shared volatile global
in multi-threaded application? I have written a small app which creates
bunch of threads and increment a volatile global the thread function will
decrment that global. When the value of the Global is is 0 then then
consumer routine will do some processing. I have tried with Global of
Volatile as well as InterlockedIncrement() function documented in MSDN but
for some reason the global is not getting updated atomically causing the
consumer thread to block forever if number of producer threads are high.
If the thread count is low then it works. I have scratched my heads for
hours but I have not been able to come up with any option to make the
increment / decrement operation of the global as atomic. Putting a
synchronization object like Critical Section will cause a lot of
contention in my program. Any help will be greatly appreciated. Please
fine the code that I have written below.
My apologies for such a long email.
------------> My Code Piece <--------------
/* File: win32.c
* Date: 11-03-2011
*
* Description: Module to test basic win32 API usage.
*/
#include <stdio.h>
#include <windows.h>
#include <strsafe.h>
#include <limits.h>
#include <listpub.h>
#define ITEM_MULTIPLIER 10
#define THREAD_MULTIPLIER 1000
/* Data Type */
/* Globals */
PTRACKER g_Finalizer;
volatile static DWORD g_dwThreadCnt = 0;
/*---------------> Routines <----------------------*/
/* Function: ModifierRoutine()
*
* Description: This routine will increment the value
* in the Array Element for which the
* index has been passed in pHeader->OperatingIndex
*
* Once it finishes, it will insert a Random Value
*/
HRESULT
WINAPI ModifierRoutine
(
LPDWORD lpIndex
)
{
DWORD Cntr = 0;
STATUS Status;
BOOL bStatus;
DWORD dwStatus,dwCntr;
HRESULT hr;
PVOID pTmpData;
PLIST_ITEM pItem;
printf("\nThreadID: %#6x \tCPU: %2d \tIndex: %5d \t[%p]",
GetCurrentThreadId(), GetCurrentProcessorNumber(),
*lpIndex, lpIndex);
/* Increment the ThreadCnt atomically */
for(dwCntr = 0; dwCntr < ITEM_MULTIPLIER; dwCntr++)
{
Status = CreateListItem(&pItem, lpIndex);
Status = AddToList(g_Finalizer, pItem);
}
hr = S_OK;
End:
//Status = BidirListTraversal(g_Finalizer);
//g_dwThreadCnt--;
__asm{
LOCK DEC DWORD PTR [g_dwThreadCnt]
}
return(hr);
}
/* Function: main()
*
*/
void main(int argc, char* argv[])
{
DWORD dwElCnt, dwCPUCnt, dwStatus, dwResult = 0;
DWORD lCntr;
BOOL bStatus;
HANDLE hTmpThread;
HANDLE *hTable;
LPDWORD *lpInputTable, lpOut = NULL, lpInput = NULL;
STATUS Status;
PLIST_ITEM pItem = NULL;
SYSTEM_INFO SystemInfo;
DWORD_PTR dwpAffinity;
char *ErrMsg0 = "\nElement count is expected as a prameter.
Terminating";
char *ErrMsg1 = "\nList Creation Failed!";
char *ErrMsg2 = "\nInsufficient Memory";
char *DispMsg0 = "\nList Item Value: %d";
char *DispMsg1 = "\nSummation: %d";
__asm
{
PUSH SIZE SYSTEM_INFO
PUSH 0
LEA EAX, DWORD PTR [SystemInfo]
PUSH EAX
CALL memset
ADD ESP, 12
LEA EAX, DWORD PTR [SystemInfo]
PUSH EAX
CALL DWORD PTR [GetNativeSystemInfo]
MOV EAX, DWORD PTR [SystemInfo+20]
MOV DWORD PTR [dwCPUCnt], EAX
// Multiply Threads THREAD_MULTIPLIER to create
// MULTIPLE threads to test the Program
XOR EDX, EDX
MOV EAX, THREAD_MULTIPLIER
MUL DWORD PTR [dwCPUCnt]
MOV DWORD PTR [dwElCnt], EAX
// Initialize the List Head
LEA EAX, DWORD PTR [g_Finalizer]
PUSH EAX
CALL CreateList
// Check Return Val of CreateList
CMP EAX, E_SUCCESS
PUSH DWORD PTR [ErrMsg1]
JNE Error
// If Jmp is Not Taken then
POP EAX
// Allocate Memory for the hTable
PUSH 4
MOV ECX, DWORD PTR [dwElCnt]
ADD ECX, 1
PUSH ECX
CALL calloc
ADD ESP, 8
MOV DWORD PTR [hTable], EAX
CMP DWORD PTR [hTable], 0
PUSH DWORD PTR [ErrMsg2]
JE Error
POP EAX
// Allocate Memory for the lpInputTable
PUSH 4
MOV ECX, DWORD PTR [dwElCnt]
ADD ECX, 1
PUSH ECX
CALL calloc
ADD ESP, 8
MOV DWORD PTR [lpInputTable], EAX
CMP DWORD PTR [lpInputTable], 0
PUSH DWORD PTR [ErrMsg2]
JE Error
POP EAX
// Move the Loop Counter in ESI
PUSH ESI
PUSH EDI
MOV DWORD PTR [lCntr], 0
// Start of the Loop
ForLoop:
MOV ESI, DWORD PTR [dwElCnt]
MOV EDI, DWORD PTR [lCntr]
CMP EDI, ESI
JGE ForLoopEnd
// Allocate Memory for input to the thread
PUSH 1
PUSH 4
CALL calloc
ADD ESP, 8
MOV DWORD PTR [lpInput], EAX
MOV ECX, DWORD PTR [lpInput]
MOV DWORD PTR [ECX], EDI
// Call CreateThread Now
PUSH 0
PUSH 0
PUSH DWORD PTR [lpInput]
LEA EDX, DWORD PTR [ModifierRoutine]
PUSH EDX
PUSH 0
PUSH 0
CALL DWORD PTR [CreateThread]
// Capture the Handle returned by CreateThread
MOV EDX, DWORD PTR [hTable]
MOV DWORD PTR [EDX+EDI*4], EAX
// Set the CPU affinity for the thread
// Calculate the CPU MASK by performin
// MOD of EDI with dwCPUCnt
PUSH EBX
MOV EBX, EAX
MOV EAX, EDI
CDQ
MOV ECX, DWORD PTR [dwCPUCnt]
IDIV ECX
PUSH EDX
PUSH EBX
CALL DWORD PTR [SetThreadAffinityMask]
MOV DWORD PTR [dwpAffinity], EAX
POP EBX
MOV EDX, DWORD PTR [lpInputTable]
MOV EAX, DWORD PTR [lpInput]
MOV DWORD PTR [EDX+EDI*4], EAX
LOCK INC DWORD PTR [g_dwThreadCnt]
ADD DWORD PTR [lCntr], 1
JMP ForLoop
ForLoopEnd:
// Restore EDI and ESI
POP EDI
POP ESI
WhileLoop:
CMP DWORD PTR [g_dwThreadCnt], 0
JNE WhileLoop
WhileEnd:
// Remove the Items
RemoveItem:
LEA ECX, DWORD PTR [pItem]
PUSH ECX
PUSH 0
PUSH DWORD PTR [g_Finalizer]
CALL RemoveFromList
// Check the return val of Remove From List
CMP EAX, E_EMPTY
JE RemoveItemEnd
LEA ECX, DWORD PTR [lpOut]
PUSH ECX
LEA EAX, DWORD PTR [pItem]
PUSH EAX
CALL DestroyListItem
MOV ECX, DWORD PTR [lpOut]
PUSH DWORD PTR [ECX]
PUSH DWORD PTR [DispMsg0]
CALL printf
ADD ESP, 8
MOV ECX, DWORD PTR [lpOut]
MOV ECX, DWORD PTR [ECX]
ADD DWORD PTR [dwResult], ECX
JMP RemoveItem
RemoveItemEnd:
PUSH DWORD PTR [dwResult]
PUSH DWORD PTR [DispMsg1]
CALL printf
ADD ESP, 8
//SAVE ESI and EDI
PUSH ESI
PUSH EDI
MOV DWORD PTR [lCntr], 0
CleanupBegin:
MOV EDI, DWORD PTR [lCntr]
MOV ESI, DWORD PTR [dwElCnt]
CMP EDI, ESI
JGE CleanupEnd
MOV ECX, DWORD PTR [hTable]
MOV ECX, DWORD PTR [ECX+EDI*4]
PUSH ECX
CALL DWORD PTR [CloseHandle]
MOV ECX, DWORD PTR [lpInputTable]
MOV ECX, DWORD PTR [ECX+EDI*4]
PUSH ECX
CALL free
ADD ESP, 4
MOV ECX, DWORD PTR [lpInputTable]
MOV DWORD PTR [ECX+EDI*4], 0
ADD DWORD PTR [lCntr], 1
JMP CleanupBegin
CleanupEnd:
POP EDI
POP ESI
JMP End
Error:
CALL printf
ADD ESP, 4
PUSH E_INVAL
CALL exit
ADD ESP,4
}
End:
;
}
/*
void main(int argc, char* argv[])
{
DWORD dwElCnt, dwCPUCnt, dwStatus, dwResult = 0;
DWORD lCntr;
BOOL bStatus;
HANDLE hTmpThread;
HANDLE *hTable;
LPDWORD *lpInputTable, lpOut = NULL;
STATUS Status;
PLIST_ITEM pItem = NULL;
SYSTEM_INFO SystemInfo;
DWORD_PTR dwpAffinity;
GetNativeSystemInfo(&SystemInfo);
dwCPUCnt = SystemInfo.dwNumberOfProcessors;
dwElCnt = dwCPUCnt * THREAD_MULTIPLIER;
Status = CreateList(&g_Finalizer);
if(E_SUCCESS != Status)
{
printf("\n[%s]:\t List Creation Failed!", __FUNCTION__);
exit(ERROR_NOT_ENOUGH_MEMORY);
}
//Allocate Space for Handle Table
hTable = (HANDLE*) calloc(dwElCnt+1, sizeof(HANDLE));
lpInputTable = (LPDWORD*) calloc(dwElCnt+1, sizeof(LPDWORD));
for(lCntr = 0; lCntr < dwElCnt; lCntr++)
{
LPDWORD lpInput = (LPDWORD) calloc(1, sizeof(DWORD));
*lpInput = lCntr;
hTmpThread = CreateThread(
NULL,
0,
ModifierRoutine,
lpInput,
0,
NULL
);
hTable[lCntr] = hTmpThread;
lpInputTable[lCntr] = lpInput;
g_dwThreadCnt++;
dwpAffinity = SetThreadAffinityMask(hTmpThread, (lCntr % dwCPUCnt));
}
while(g_dwThreadCnt)
{}
Status = RemoveFromList(g_Finalizer, NULL, &pItem);
while(E_EMPTY != Status)
{
Status = DestroyListItem(&pItem, &lpOut);
printf("\nList Item Value: %d", *lpOut);
dwResult += (*lpOut);
Status = RemoveFromList(g_Finalizer, NULL, &pItem);
}
printf("\nSummation: %d", dwResult);
// Close Handles to the thread
Cleanup:
for(lCntr = 0; lCntr <= dwElCnt; lCntr++)
{
CloseHandle(hTable[lCntr]);
free(lpInputTable[lCntr]);
lpInputTable[lCntr] = NULL;
}
}
*/
Thanks & Regards
Pushkar Prasad
What is the best way to ensure atomic update to a shared volatile global
in multi-threaded application? I have written a small app which creates
bunch of threads and increment a volatile global the thread function will
decrment that global. When the value of the Global is is 0 then then
consumer routine will do some processing. I have tried with Global of
Volatile as well as InterlockedIncrement() function documented in MSDN but
for some reason the global is not getting updated atomically causing the
consumer thread to block forever if number of producer threads are high.
If the thread count is low then it works. I have scratched my heads for
hours but I have not been able to come up with any option to make the
increment / decrement operation of the global as atomic. Putting a
synchronization object like Critical Section will cause a lot of
contention in my program. Any help will be greatly appreciated. Please
fine the code that I have written below.
My apologies for such a long email.
------------> My Code Piece <--------------
/* File: win32.c
* Date: 11-03-2011
*
* Description: Module to test basic win32 API usage.
*/
#include <stdio.h>
#include <windows.h>
#include <strsafe.h>
#include <limits.h>
#include <listpub.h>
#define ITEM_MULTIPLIER 10
#define THREAD_MULTIPLIER 1000
/* Data Type */
/* Globals */
PTRACKER g_Finalizer;
volatile static DWORD g_dwThreadCnt = 0;
/*---------------> Routines <----------------------*/
/* Function: ModifierRoutine()
*
* Description: This routine will increment the value
* in the Array Element for which the
* index has been passed in pHeader->OperatingIndex
*
* Once it finishes, it will insert a Random Value
*/
HRESULT
WINAPI ModifierRoutine
(
LPDWORD lpIndex
)
{
DWORD Cntr = 0;
STATUS Status;
BOOL bStatus;
DWORD dwStatus,dwCntr;
HRESULT hr;
PVOID pTmpData;
PLIST_ITEM pItem;
printf("\nThreadID: %#6x \tCPU: %2d \tIndex: %5d \t[%p]",
GetCurrentThreadId(), GetCurrentProcessorNumber(),
*lpIndex, lpIndex);
/* Increment the ThreadCnt atomically */
for(dwCntr = 0; dwCntr < ITEM_MULTIPLIER; dwCntr++)
{
Status = CreateListItem(&pItem, lpIndex);
Status = AddToList(g_Finalizer, pItem);
}
hr = S_OK;
End:
//Status = BidirListTraversal(g_Finalizer);
//g_dwThreadCnt--;
__asm{
LOCK DEC DWORD PTR [g_dwThreadCnt]
}
return(hr);
}
/* Function: main()
*
*/
void main(int argc, char* argv[])
{
DWORD dwElCnt, dwCPUCnt, dwStatus, dwResult = 0;
DWORD lCntr;
BOOL bStatus;
HANDLE hTmpThread;
HANDLE *hTable;
LPDWORD *lpInputTable, lpOut = NULL, lpInput = NULL;
STATUS Status;
PLIST_ITEM pItem = NULL;
SYSTEM_INFO SystemInfo;
DWORD_PTR dwpAffinity;
char *ErrMsg0 = "\nElement count is expected as a prameter.
Terminating";
char *ErrMsg1 = "\nList Creation Failed!";
char *ErrMsg2 = "\nInsufficient Memory";
char *DispMsg0 = "\nList Item Value: %d";
char *DispMsg1 = "\nSummation: %d";
__asm
{
PUSH SIZE SYSTEM_INFO
PUSH 0
LEA EAX, DWORD PTR [SystemInfo]
PUSH EAX
CALL memset
ADD ESP, 12
LEA EAX, DWORD PTR [SystemInfo]
PUSH EAX
CALL DWORD PTR [GetNativeSystemInfo]
MOV EAX, DWORD PTR [SystemInfo+20]
MOV DWORD PTR [dwCPUCnt], EAX
// Multiply Threads THREAD_MULTIPLIER to create
// MULTIPLE threads to test the Program
XOR EDX, EDX
MOV EAX, THREAD_MULTIPLIER
MUL DWORD PTR [dwCPUCnt]
MOV DWORD PTR [dwElCnt], EAX
// Initialize the List Head
LEA EAX, DWORD PTR [g_Finalizer]
PUSH EAX
CALL CreateList
// Check Return Val of CreateList
CMP EAX, E_SUCCESS
PUSH DWORD PTR [ErrMsg1]
JNE Error
// If Jmp is Not Taken then
POP EAX
// Allocate Memory for the hTable
PUSH 4
MOV ECX, DWORD PTR [dwElCnt]
ADD ECX, 1
PUSH ECX
CALL calloc
ADD ESP, 8
MOV DWORD PTR [hTable], EAX
CMP DWORD PTR [hTable], 0
PUSH DWORD PTR [ErrMsg2]
JE Error
POP EAX
// Allocate Memory for the lpInputTable
PUSH 4
MOV ECX, DWORD PTR [dwElCnt]
ADD ECX, 1
PUSH ECX
CALL calloc
ADD ESP, 8
MOV DWORD PTR [lpInputTable], EAX
CMP DWORD PTR [lpInputTable], 0
PUSH DWORD PTR [ErrMsg2]
JE Error
POP EAX
// Move the Loop Counter in ESI
PUSH ESI
PUSH EDI
MOV DWORD PTR [lCntr], 0
// Start of the Loop
ForLoop:
MOV ESI, DWORD PTR [dwElCnt]
MOV EDI, DWORD PTR [lCntr]
CMP EDI, ESI
JGE ForLoopEnd
// Allocate Memory for input to the thread
PUSH 1
PUSH 4
CALL calloc
ADD ESP, 8
MOV DWORD PTR [lpInput], EAX
MOV ECX, DWORD PTR [lpInput]
MOV DWORD PTR [ECX], EDI
// Call CreateThread Now
PUSH 0
PUSH 0
PUSH DWORD PTR [lpInput]
LEA EDX, DWORD PTR [ModifierRoutine]
PUSH EDX
PUSH 0
PUSH 0
CALL DWORD PTR [CreateThread]
// Capture the Handle returned by CreateThread
MOV EDX, DWORD PTR [hTable]
MOV DWORD PTR [EDX+EDI*4], EAX
// Set the CPU affinity for the thread
// Calculate the CPU MASK by performin
// MOD of EDI with dwCPUCnt
PUSH EBX
MOV EBX, EAX
MOV EAX, EDI
CDQ
MOV ECX, DWORD PTR [dwCPUCnt]
IDIV ECX
PUSH EDX
PUSH EBX
CALL DWORD PTR [SetThreadAffinityMask]
MOV DWORD PTR [dwpAffinity], EAX
POP EBX
MOV EDX, DWORD PTR [lpInputTable]
MOV EAX, DWORD PTR [lpInput]
MOV DWORD PTR [EDX+EDI*4], EAX
LOCK INC DWORD PTR [g_dwThreadCnt]
ADD DWORD PTR [lCntr], 1
JMP ForLoop
ForLoopEnd:
// Restore EDI and ESI
POP EDI
POP ESI
WhileLoop:
CMP DWORD PTR [g_dwThreadCnt], 0
JNE WhileLoop
WhileEnd:
// Remove the Items
RemoveItem:
LEA ECX, DWORD PTR [pItem]
PUSH ECX
PUSH 0
PUSH DWORD PTR [g_Finalizer]
CALL RemoveFromList
// Check the return val of Remove From List
CMP EAX, E_EMPTY
JE RemoveItemEnd
LEA ECX, DWORD PTR [lpOut]
PUSH ECX
LEA EAX, DWORD PTR [pItem]
PUSH EAX
CALL DestroyListItem
MOV ECX, DWORD PTR [lpOut]
PUSH DWORD PTR [ECX]
PUSH DWORD PTR [DispMsg0]
CALL printf
ADD ESP, 8
MOV ECX, DWORD PTR [lpOut]
MOV ECX, DWORD PTR [ECX]
ADD DWORD PTR [dwResult], ECX
JMP RemoveItem
RemoveItemEnd:
PUSH DWORD PTR [dwResult]
PUSH DWORD PTR [DispMsg1]
CALL printf
ADD ESP, 8
//SAVE ESI and EDI
PUSH ESI
PUSH EDI
MOV DWORD PTR [lCntr], 0
CleanupBegin:
MOV EDI, DWORD PTR [lCntr]
MOV ESI, DWORD PTR [dwElCnt]
CMP EDI, ESI
JGE CleanupEnd
MOV ECX, DWORD PTR [hTable]
MOV ECX, DWORD PTR [ECX+EDI*4]
PUSH ECX
CALL DWORD PTR [CloseHandle]
MOV ECX, DWORD PTR [lpInputTable]
MOV ECX, DWORD PTR [ECX+EDI*4]
PUSH ECX
CALL free
ADD ESP, 4
MOV ECX, DWORD PTR [lpInputTable]
MOV DWORD PTR [ECX+EDI*4], 0
ADD DWORD PTR [lCntr], 1
JMP CleanupBegin
CleanupEnd:
POP EDI
POP ESI
JMP End
Error:
CALL printf
ADD ESP, 4
PUSH E_INVAL
CALL exit
ADD ESP,4
}
End:
;
}
/*
void main(int argc, char* argv[])
{
DWORD dwElCnt, dwCPUCnt, dwStatus, dwResult = 0;
DWORD lCntr;
BOOL bStatus;
HANDLE hTmpThread;
HANDLE *hTable;
LPDWORD *lpInputTable, lpOut = NULL;
STATUS Status;
PLIST_ITEM pItem = NULL;
SYSTEM_INFO SystemInfo;
DWORD_PTR dwpAffinity;
GetNativeSystemInfo(&SystemInfo);
dwCPUCnt = SystemInfo.dwNumberOfProcessors;
dwElCnt = dwCPUCnt * THREAD_MULTIPLIER;
Status = CreateList(&g_Finalizer);
if(E_SUCCESS != Status)
{
printf("\n[%s]:\t List Creation Failed!", __FUNCTION__);
exit(ERROR_NOT_ENOUGH_MEMORY);
}
//Allocate Space for Handle Table
hTable = (HANDLE*) calloc(dwElCnt+1, sizeof(HANDLE));
lpInputTable = (LPDWORD*) calloc(dwElCnt+1, sizeof(LPDWORD));
for(lCntr = 0; lCntr < dwElCnt; lCntr++)
{
LPDWORD lpInput = (LPDWORD) calloc(1, sizeof(DWORD));
*lpInput = lCntr;
hTmpThread = CreateThread(
NULL,
0,
ModifierRoutine,
lpInput,
0,
NULL
);
hTable[lCntr] = hTmpThread;
lpInputTable[lCntr] = lpInput;
g_dwThreadCnt++;
dwpAffinity = SetThreadAffinityMask(hTmpThread, (lCntr % dwCPUCnt));
}
while(g_dwThreadCnt)
{}
Status = RemoveFromList(g_Finalizer, NULL, &pItem);
while(E_EMPTY != Status)
{
Status = DestroyListItem(&pItem, &lpOut);
printf("\nList Item Value: %d", *lpOut);
dwResult += (*lpOut);
Status = RemoveFromList(g_Finalizer, NULL, &pItem);
}
printf("\nSummation: %d", dwResult);
// Close Handles to the thread
Cleanup:
for(lCntr = 0; lCntr <= dwElCnt; lCntr++)
{
CloseHandle(hTable[lCntr]);
free(lpInputTable[lCntr]);
lpInputTable[lCntr] = NULL;
}
}
*/
Thanks & Regards
Pushkar Prasad