多线程高效访问共享资源
多线程高效访问共享资源
-------BY 懒牛
一、多线程访问共享资源需注意的问题:
1、多个线程如果在一个进程内,可共享进程内的资源。
2、多个线程在不同的进程内,不同进程的资源不能直接共享,要用到内存映射文件。
3、访问方式是用户方式还是内核方式,用户方式速度快,内核方式速度慢。
二、用户方式和内核方式的区别:
1、书上说的用户方式有互锁函数,CRITICAL_SECTION关键代码段,内核方式有信标、事件、互斥量。但是关键代码段也不是完全的用户方式,当一个线程进入关键代码段访问资源,另一个线程是进入内核方式等待的。也是很费时间。经过理解,我认为:用户方式是指当一个线程占有资源时,另外的线程是以什么方式在等待。
2、平常让程序等待的方式有三种,一种是让程序不断的条件循环,当条件达到时,退出。这是最标准的用户方式,依靠循环浪费时间来等待,没用到内核。第二种用sleep()函数,这个不太好用条件控制,本质上也是内核方式。第三种就是内核的Waitfor…..之类的等待函数了。
3、从浪费时间的程度上来看,要分CPU是单核还是多核的,如果是单核的,用户方式的条件循环最浪费时间,因为单核时循环要不断的在几个线程上下文切换特浪费时间;内核方式其次,所以在单核情况下,我们多线程访问资源,就直接用内核方式就OK了。在多核方式下情况有变化,一个线程在第一个CPU上访问资源时,另一个线程可以使用第二个CPU来在用户方式下做条件循环来判断是否能够访问资源了。多核方式下是真正的并发访问,二个线程同时运行,不用做上下文切换。如果这种访问资源的时间用时很短,比如说:一个线程只是在资源上做一个简单运算就离开,则另一个线程在几个用户循环的判断时间就能够访问资源了,何苦要进入内核方式呢?所以在对共享资源访问时,我们先在用户方式下做几个循环来判断,如果能访问了,OK进行访问,如果循环到一定时间,还是不能访问资源,说明这种资源的访问很费时间,在用户方式下继续做循环不合算了,我们就换成内核方式,让系统去帮我等待去吧,我们不管了,哈哈!
三、多线程访问共享资源总结如下:
1、单核时直接进入内核方式等待,等待成功则访问资源。
2、多核时先在用户方式下做循环不断的询问是否能访问,到达一定循环次数,如果条件满足则访问,如果到循环最大值仍然不能访问,则我们也不浪费时间,直接转内核方式等待。
WINDOWS核心编程中为高效访问共享资源,编制了COptex类,使用这个类建立的对象就是多个线程要访问的共享资源,下面让我们来详细分析一下:
表1-1 成员变量描述
若要使用o p t e x,只需要声明一个C O p t e x对象。该对象有3个构造函数:
COptex::(DWORD dwSpinCount = 4000);
COptex::(PCSTR pszName, DWORD dwSpinCount = 4000);
COptex::(PCWSTR pszName, DWORD dwSpinCount = 4000);
第一个构造函数用于创建只能用来对单个进程中的各个线程进行同步的C O p t e x对象。这种类型的o p t e x占用的开销比跨进程的o p t e x要少得多。另外两个构造函数可以用来创建在多个进程中的线程之间实现同步的o p t e x。对于p s z N a m e参数,必须传递一个A N S I或U n i c o d e字符串,该字符串用于对每个共享的o p t e x进行标识。若要使两个或多个进程共享一个o p t e x,那么两个进程必须建立一个C O p t e x对象的实例,并且传递相同的字符串名字。
如果线程要进入和退出C O p t e x对象,要调用E n t e r和l e a v e方法:
void COptex::Enter();
void COptex::Leave();
现在重点来看看Enter()的代码,下面用流程图来演示
void COptex::Enter() {
// Spin, trying to get the optex
if (TryEnter()) //没进内核之前,先要在用户方式试一下,在用户方式能进,则进入。
return; // We got it, return
// We couldn't get the optex, wait for it.
DWORD dwThreadId = GetCurrentThreadId();
//这里m_psi->m_lLockCount加1,有二层意思,如果刚好拥有optex 的线程此时释放
//另外也没有其它线程在内核方式等待,则m_psi->m_lLockCount=0,加1后返回1条件成立//不用进内核方式了,直接可以拥有optex了,这种机遇很小。最主要的意思是
//m_psi->m_lLockCount加1,表明线程在内核方式下正在等待1次
if (InterlockedIncrement(&m_psi->m_lLockCount) == 1) {
// Optex is unowned, let this thread own it once
m_psi->m_dwThreadId = dwThreadId;
m_psi->m_lRecurseCount = 1;
} else {
if (m_psi->m_dwThreadId == dwThreadId) {
// If optex is owned by this thread, own it again
m_psi->m_lRecurseCount++;
} else {
// Optex is owned by another thread, wait for it
WaitForSingleObject(m_hevt, INFINITE);
// Optex is unowned, let this thread own it once
m_psi->m_dwThreadId = dwThreadId;
m_psi->m_lRecurseCount = 1;
}
}
}
///////////////////////////////////////////////////////////////////////////////
BOOL COptex::TryEnter() {
DWORD dwThreadId = GetCurrentThreadId();//得到当前激活的线程的线程ID
//这个变量判断线程是否拥有optex,初始化为不拥有
BOOL fThisThreadOwnsTheOptex = FALSE; // Assume a thread owns the optex
//初始化用户方式循环次数
DWORD dwSpinCount = m_psi->m_dwSpinCount; // How many times to spin
//开始循环判断
do {
// If lock count = 0, optex is unowned, we can own it
fThisThreadOwnsTheOptex = (0 ==
InterlockedCompareExchange(&m_psi->m_lLockCount, 1, 0));
//互锁函数,看m_psi->m_lLockCount变量是否为0,为0则换成1,返回0,条件
//为真,则fThisThreadOwnsTheOptex为真。这种情况发生有几下几种情况:
//线程第一次进入optex,m_psi->m_lLockCount肯定为0条件成立,没有其它线程//在内核方式下等待,拥有optex的线程配对释放leave()后,m_lLockCount和m_lRecurseCount //都为0
if (fThisThreadOwnsTheOptex) {
//当前线程拥有optex
// Optex is unowned, let this thread own it once
m_psi->m_dwThreadId = dwThreadId;
m_psi->m_lRecurseCount = 1; //尝试一次,拥有一次,m_lLockCount和
//m_lRecurseCount成对增加
} else {
//此线程已经拥有optex,第二次进入,测试有用,意义不大,代码表明,只要在拥有//optex期间,可以多次重复进入optex,同时尝试次数和拥有次数一起加1
if (m_psi->m_dwThreadId == dwThreadId) {
// If optex is owned by this thread, own it again
InterlockedIncrement(&m_psi->m_lLockCount);
m_psi->m_lRecurseCount++;
fThisThreadOwnsTheOptex = TRUE;
}
}
} while (!fThisThreadOwnsTheOptex && (dwSpinCount-- > 0));
// Return whether or not this thread owns the optex
return(fThisThreadOwnsTheOptex);
}
在这里要注意的是,为什么拥有次数m_psi->m_lRecurseCount只是简单的增加,而尝试次数m_psi->m_lLockCount则要用互锁函数保护呢?
答:因为m_psi->m_lRecurseCount变量总是在线程拥有optex 时才会增加或减少,而同时只有一个线程进入optex,所以不会发生多个线程同时对m_psi->m_lRecurseCount更改的情况,不用原子操作来保护。
而m_psi->m_lLockCount则不然,多个线程在进入内核方式等待前要增加m_psi->m_lLockCount,拥有optex的线程在重复进入时也要增加,同时有多个线程都能访问更改变量,则要用互锁函数锁定。
清单1-1 optex示例应用程序全部代码
/****************************************************************************** Module: Optex.cpp
Notices: Copyright (c) 2000 Jeffrey Richter
******************************************************************************/
#include "..\CmnHdr.h" /* See Appendix A. */
#include "Optex.h"
///////////////////////////////////////////////////////////////////////////////
// 0=multi-CPU, 1=single-CPU, -1=not set yet
BOOL COptex::sm_fUniprocessorHost = -1;
///////////////////////////////////////////////////////////////////////////////
PSTR COptex::ConstructObjectName(PSTR pszResult,
PCSTR pszPrefix, BOOL fUnicode, PVOID pszName) {
pszResult[0] = 0;
if (pszName == NULL)
return(NULL);
wsprintfA(pszResult, fUnicode ? "%s%S" : "%s%s", pszPrefix, pszName);
return(pszResult);
}
///////////////////////////////////////////////////////////////////////////////
void COptex::CommonConstructor(DWORD dwSpinCount,
BOOL fUnicode, PVOID pszName) {
if (sm_fUniprocessorHost == -1) {
// This is the 1st object constructed, get the number of CPUs
SYSTEM_INFO sinf;
GetSystemInfo(&sinf);
sm_fUniprocessorHost = (sinf.dwNumberOfProcessors == 1);
}
m_hevt = m_hfm = NULL;
m_psi = NULL;
if (pszName == NULL) { // Creating a single-process optex
m_hevt = CreateEventA(NULL, FALSE, FALSE, NULL);
chASSERT(m_hevt != NULL);
m_psi = new SHAREDINFO;
chASSERT(m_psi != NULL);
ZeroMemory(m_psi, sizeof(*m_psi));
} else { // Creating a cross-process optex
// Always use ANSI so that this works on Win9x and Windows 2000
char szResult[100];
ConstructObjectName(szResult, "Optex_Event_", fUnicode, pszName);
m_hevt = CreateEventA(NULL, FALSE, FALSE, szResult);
chASSERT(m_hevt != NULL);
ConstructObjectName(szResult, "Optex_MMF_", fUnicode, pszName);
m_hfm = CreateFileMappingA(INVALID_HANDLE_VALUE, NULL,
PAGE_READWRITE, 0, sizeof(*m_psi), szResult);
chASSERT(m_hfm != NULL);
m_psi = (PSHAREDINFO) MapViewOfFile(m_hfm,
FILE_MAP_WRITE, 0, 0, 0);
chASSERT(m_psi != NULL);
// Note: SHAREDINFO's m_lLockCount, m_dwThreadId, and m_lRecurseCount
// members need to be initialized to 0. Fortunately, a new pagefile
// MMF sets all of its data to 0 when created. This saves use from
// some thread synchronization work.
}
SetSpinCount(dwSpinCount);
}
/////////////////////////////////////////////////////////////////////////////// COptex::~COptex() {
#ifdef _DEBUG
if (IsSingleProcessOptex() && (m_psi->m_dwThreadId != 0)) {
// A single-process optex shouldn't be destroyed if any thread owns it
DebugBreak();
}
if (!IsSingleProcessOptex() &&
(m_psi->m_dwThreadId == GetCurrentThreadId())) {
// A cross-process optex shouldn't be destroyed if our thread owns it
DebugBreak();
}
#endif
CloseHandle(m_hevt);
if (IsSingleProcessOptex()) {
delete m_psi;
} else {
UnmapViewOfFile(m_psi);
CloseHandle(m_hfm);
}
}
/////////////////////////////////////////////////////////////////////////////// void COptex::SetSpinCount(DWORD dwSpinCount) {
// No spinning on single CPU machines
if (!sm_fUniprocessorHost)
InterlockedExchangePointer((PVOID*) &m_psi->m_dwSpinCount,
(PVOID) (DWORD_PTR) dwSpinCount);
}
/////////////////////////////////////////////////////////////////////////////// void COptex::Enter() {
// Spin, trying to get the optex
if (TryEnter())
return; // We got it, return
// We couldn't get the optex, wait for it.
DWORD dwThreadId = GetCurrentThreadId();
if (InterlockedIncrement(&m_psi->m_lLockCount) == 1) {
// Optex is unowned, let this thread own it once
m_psi->m_dwThreadId = dwThreadId;
m_psi->m_lRecurseCount = 1;
} else {
if (m_psi->m_dwThreadId == dwThreadId) {
// If optex is owned by this thread, own it again
m_psi->m_lRecurseCount++;
} else {
// Optex is owned by another thread, wait for it
WaitForSingleObject(m_hevt, INFINITE);
// Optex is unowned, let this thread own it once
m_psi->m_dwThreadId = dwThreadId;
m_psi->m_lRecurseCount = 1;
}
}
}
/////////////////////////////////////////////////////////////////////////////// BOOL COptex::TryEnter() {
DWORD dwThreadId = GetCurrentThreadId();
BOOL fThisThreadOwnsTheOptex = FALSE; // Assume a thread owns the optex DWORD dwSpinCount = m_psi->m_dwSpinCount; // How many times to spin
do {
// If lock count = 0, optex is unowned, we can own it
fThisThreadOwnsTheOptex = (0 ==
InterlockedCompareExchange(&m_psi->m_lLockCount, 1, 0));
if (fThisThreadOwnsTheOptex) {
// Optex is unowned, let this thread own it once
m_psi->m_dwThreadId = dwThreadId;
m_psi->m_lRecurseCount = 1;
} else {
if (m_psi->m_dwThreadId == dwThreadId) {
// If optex is owned by this thread, own it again
InterlockedIncrement(&m_psi->m_lLockCount);
m_psi->m_lRecurseCount++;
fThisThreadOwnsTheOptex = TRUE;
}
}
} while (!fThisThreadOwnsTheOptex && (dwSpinCount-- > 0));
// Return whether or not this thread owns the optex
return(fThisThreadOwnsTheOptex);
}
/////////////////////////////////////////////////////////////////////////////// void COptex::Leave() {
#ifdef _DEBUG
// Only the owning thread can leave the optex
if (m_psi->m_dwThreadId != GetCurrentThreadId())
DebugBreak();
#endif
// Reduce this thread's ownership of the optex
if (--m_psi->m_lRecurseCount > 0) {
// We still own the optex
InterlockedDecrement(&m_psi->m_lLockCount);
} else {
// We don't own the optex anymore
m_psi->m_dwThreadId = 0;
if (InterlockedDecrement(&m_psi->m_lLockCount) > 0) {
// Other threads are waiting, the auto-reset event wakes one of them SetEvent(m_hevt);
}
}
}
//////////////////////////////// End of File ////////////////////////////////// /******************************************************************************
Module name: Optex.h
Written by: Jeffrey Richter
******************************************************************************/ #pragma once
///////////////////////////////////////////////////////////////////////////////
class COptex {
public:
COptex(DWORD dwSpinCount = 4000);
COptex(PCSTR pszName, DWORD dwSpinCount = 4000);
COptex(PCWSTR pszName, DWORD dwSpinCount = 4000);
~COptex();
void SetSpinCount(DWORD dwSpinCount);
void Enter();
BOOL TryEnter();
void Leave();
BOOL IsSingleProcessOptex() const;
private:
typedef struct {
DWORD m_dwSpinCount;
long m_lLockCount;
DWORD m_dwThreadId;
long m_lRecurseCount;
} SHAREDINFO, *PSHAREDINFO;
HANDLE m_hevt;
HANDLE m_hfm;
PSHAREDINFO m_psi;
private:
static BOOL sm_fUniprocessorHost;
private:
void CommonConstructor(DWORD dwSpinCount, BOOL fUnicode, PVOID pszName);
PSTR ConstructObjectName(PSTR pszResult,
PCSTR pszPrefix, BOOL fUnicode, PVOID pszName);
};
/////////////////////////////////////////////////////////////////////////////// inline COptex::COptex(DWORD dwSpinCount) {
CommonConstructor(dwSpinCount, FALSE, NULL);
}
/////////////////////////////////////////////////////////////////////////////// inline COptex::COptex(PCSTR pszName, DWORD dwSpinCount) {
CommonConstructor(dwSpinCount, FALSE, (PVOID) pszName);
}
/////////////////////////////////////////////////////////////////////////////// inline COptex::COptex(PCWSTR pszName, DWORD dwSpinCount) {
CommonConstructor(dwSpinCount, TRUE, (PVOID) pszName);
}
/////////////////////////////////////////////////////////////////////////////// inline COptex::IsSingleProcessOptex() const {
return(m_hfm == NULL);
}
///////////////////////////////// End of File ///////////////////////////////// //Microsoft Developer Studio generated resource script.
//
#include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS
///////////////////////////////////////////////////////////////////////////// //
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"
///////////////////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS
///////////////////////////////////////////////////////////////////////////// // English (U.S.) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
#ifdef _WIN32
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#endif //_WIN32
///////////////////////////////////////////////////////////////////////////// //
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_OPTEX ICON DISCARDABLE "Optex.ICO"
#ifdef APSTUDIO_INVOKED
///////////////////////////////////////////////////////////////////////////// //
// TEXTINCLUDE
//
1 TEXTINCLUDE DISCARDABLE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE DISCARDABLE
BEGIN
"#include ""afxres.h""\r\n"
"\0"
END
3 TEXTINCLUDE DISCARDABLE
BEGIN
"\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
#endif // English (U.S.) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
///////////////////////////////////////////////////////////////////////////// //
// Generated from the TEXTINCLUDE 3 resource.
//
///////////////////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
/****************************************************************************** Module name: OptexTest.cpp
Written by: Jeffrey Richter
******************************************************************************/
#include "..\CmnHdr.h" /* See Appendix A. */
#include
#include
#include "Optex.h"
/////////////////////////////////////////////////////////////////////////////// DWORD WINAPI SecondFunc(PVOID pvParam) {
COptex& optex = * (COptex*) pvParam;
// The primary thread should own the optex here, this should fail
chVERIFY(optex.TryEnter() == FALSE);
// Wait for the primary thread to give up the optex
optex.Enter();
optex.Enter(); // Test recursive ownership
chMB("Secondary: Entered the optex\n(Dismiss me 2nd)");
// Leave the optex but we still own it once
optex.Leave();
chMB("Secondary: The primary thread should not display a box yet");
optex.Leave(); // The primary thread should be able to run now
return(0);
/////////////////////////////////////////////////////////////////////////////// VOID FirstFunc(BOOL fLocal, COptex& optex) {
optex.Enter(); // Gain ownership of the optex
// Since this thread owns the optex, we should be able to get it again
chVERIFY(optex.TryEnter());
HANDLE hOtherThread = NULL;
if (fLocal) {
// Spawn a secondary thread for testing purposes (pass it the optex)
DWORD dwThreadId;
hOtherThread = chBEGINTHREADEX(NULL, 0,
SecondFunc, (PVOID) &optex, 0, &dwThreadId);
} else {
// Spawn a secondary process for testing purposes
STARTUPINFO si = { sizeof(si) };
PROCESS_INFORMATION pi;
TCHAR szPath[MAX_PATH];
GetModuleFileName(NULL, szPath, chDIMOF(szPath));
CreateProcess(NULL, szPath, NULL, NULL,
FALSE, 0, NULL, NULL, &si, &pi);
hOtherThread = pi.hProcess;
CloseHandle(pi.hThread);
/*在这里要注意,createProcess生成新的进程时,子进程和子进程的主线程内核计数是2,因为建立子进程//是1次,而后打开子进程取出信息填写pi结构是1次,共计2次,所以在这里先调用closehandle关掉子进程的主线程句柄,让计数变成1,hProcess不能关下面还要用,用完再关,这样当子进程正常退出时,进程和主线程内核计数各减1次,内核计数为0内核释放*/
}
// Wait for the second thread to own the optex
chMB("Primary: Hit OK to give optex to secondary");
// Let the second thread gain ownership of the optex
optex.Leave();
optex.Leave();
// Wait for the second thread to own the optex
chMB("Primary: Hit OK to wait for the optex\n(Dismiss me 1st)");
optex.Enter(); // Try to gain ownership back
WaitForSingleObject(hOtherThread, INFINITE);
CloseHandle(hOtherThread);
optex.Leave();
}
/////////////////////////////////////////////////////////////////////////////// int WINAPI _tWinMain(HINSTANCE hinstExe, HINSTANCE, PTSTR pszCmdLine, int) {
// This event is just used to determine which instance this is.
HANDLE hevt = CreateEvent(NULL, FALSE, FALSE, TEXT("OptexTest"));
//这里是判断运行的进程是第几个实例,如果是第二个,转到else 处理
if (GetLastError() != ERROR_ALREADY_EXISTS) {
// This is the first instance of this test application
// First, let's test the single-process optex
COptex optexSingle; // Create a single-process optex
FirstFunc(TRUE, optexSingle);
// Now, let's test the cross-process optex
COptex optexCross("CrossOptexTest"); // Create a cross-process optex
FirstFunc(FALSE, optexCross);
} else {
// This is the second instance of this test application
DebugBreak(); // Force debugger connection for tracing
//DebugBreak强制进入调示模式
// Test the cross-process optex
COptex optexCross("CrossOptexTest"); // Create a cross-process optex
SecondFunc((PVOID) &optexCross);
}
CloseHandle(hevt);
return(0);
}
///////////////////////////////// End of File /////////////////////////////////