Windows核心编程学习七:用户模式下的线程同步

来源:互联网 发布:男士西服知乎 编辑:程序博客网 时间:2024/05/29 15:07

注:源码为学习《Windows核心编程》的一些尝试,非原创。若能有助于一二访客,幸甚。


1.基本框架

/* * File:CQueue.cpp * Time:2013-07-08 * 描述:学习《Windows核心编程》 */#include "Queue.h"#include <tchar.h>#include <windowsx.h>#include "resource.h"/************************************************************************/#define chHANDLE_DLGMSG(hWnd, message, fn)                 \   case (message): return (SetDlgMsgResult(hWnd, uMsg,     \      HANDLE_##message((hWnd), (wParam), (lParam), (fn))))// Sets the dialog box iconsinline void chSETDLGICONS(HWND hWnd, int idi) {   SendMessage(hWnd, WM_SETICON, ICON_BIG,  (LPARAM)       LoadIcon((HINSTANCE) GetWindowLongPtr(hWnd, GWLP_HINSTANCE), MAKEINTRESOURCE(idi)));   SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)       LoadIcon((HINSTANCE) GetWindowLongPtr(hWnd, GWLP_HINSTANCE), MAKEINTRESOURCE(idi)));}/************************************************************************/BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam){chSETDLGICONS(hWnd, IDI_QUEUE);return TRUE;}void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify){switch (id){case IDCANCEL:EndDialog(hWnd, id);break;}}INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){switch (uMsg){chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog);chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);}return FALSE;}/*************************************************************************/int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int){DialogBox(hInstExe, MAKEINTRESOURCE(IDD_QUEUE), NULL, Dlg_Proc);return 0;}



2.队列的实现

此处的队列并不具有线程安全性,由客户线程和服务器线程来负责他们访问的全局队列进行同步

/* * File:CQueue.h * Time:2013-07-08 * 描述:学习《Windows核心编程》 */#ifndef _CQUEUE_H_#define _CQUEUE_H_#include <windows.h>class CQueue{public:struct ELEMENT {int m_nThreadNum;// 线程号int m_nRequestNum;// 请求号};typedef ELEMENT* PELEMENT;private:struct INNER_ELEMENT {intm_nStamp;// 记录插入顺序,添加元素时递增,0表示为空ELEMENTm_element;};typedef INNER_ELEMENT* PINNER_ELEMENT;private:PINNER_ELEMENTm_pElements;// 队列元素数组intm_nMaxElements;// 数组长度intm_nCurrentStamp;// 记录当前插入的元素,添加元素时递增private:intGetFreeSlot();// 返回第一个m_nStamp为0(表示内容已经被读取或者内容为空)的元素索引intGetNextSlot(int nThreadNum);// 返回需要线程nThreadNum处理的m_nStamp值最小(表示最早添加)且不为0(空闲或已读)的元素索引public:CQueue(int nMaxElements);~CQueue();BOOL IsFull();// 队列是否已满BOOL IsEmpty(int nThreadNum);// 队列中是否存在线程号为nThreadNum处理的元素void AddElement(ELEMENT e);// 添加元素到队列BOOL GetNewElement(int nThreadNum, ELEMENT& e);// 获取队列中需要线程号为nThreadNum处理的最早插入的元素};#endif

CQueue::CQueue(int nMaxElements){// 为队列分配空间并初始化队列元素m_pElements = (PINNER_ELEMENT)HeapAlloc(GetProcessHeap(), 0, sizeof(INNER_ELEMENT) * nMaxElements);ZeroMemory(m_pElements, sizeof(INNER_ELEMENT) * nMaxElements);// 初始化元素计数m_nCurrentStamp = 0;// 保存队列最大元素数m_nMaxElements = nMaxElements;}CQueue::~CQueue(){// 释放空间HeapFree(GetProcessHeap(), 0, m_pElements);}// 队列是否已满BOOL CQueue::IsFull(){return (GetFreeSlot() == -1);}// 队列中是否存在线程号为nThreadNum处理的元素BOOL CQueue::IsEmpty(int nThreadNum){return (GetNextSlot(nThreadNum) == -1);}// 返回第一个m_nStamp为0(表示内容已经被读取或者内容为空)的元素索引int CQueue::GetFreeSlot(){// 寻找stamp为0 的元素索引for (int current = 0; current < m_nMaxElements; current++){if (m_pElements[current].m_nStamp == 0)return current;}// 没有找到空余位置return -1;}// 返回需要线程nThreadNum处理的m_nStamp值最小(表示最早添加)且不为0(空闲或已读)的元素索引int CQueue::GetNextSlot(int nThreadNum){// 默认没有需要线程nThreadNum处理的元素int firstSlot = -1;// m_nCurrentStamp为最后添加的元素的stamp,为最大的stampint firstStamp = m_nCurrentStamp + 1;for (int current = 0; current < m_nMaxElements; current++){if ((m_pElements[current].m_nStamp != 0) &&// 不是空位置((m_pElements[current].m_element.m_nRequestNum % 2) == nThreadNum) &&// 需要线程nThreadNum处理(m_pElements[current].m_nStamp < firstStamp))// 比当前找到的stamp小{firstStamp = m_pElements[current].m_nStamp;// 更新firstSlot  = current;}}return firstSlot;}// 添加元素到队列void CQueue::AddElement(ELEMENT e){// 如果队列已满,返回int nFreeSlot = GetFreeSlot();if (nFreeSlot == -1)return;// 拷贝元素m_pElements[nFreeSlot].m_element = e;// 记录插入顺序m_pElements[nFreeSlot].m_nStamp = ++m_nCurrentStamp;}// 获取队列中需要线程号为nThreadNum处理的最早插入的元素BOOL CQueue::GetNewElement(int nThreadNum, ELEMENT& e){// 没有找到需要线程nThreadNum处理的元素int nNewSlot = GetNextSlot(nThreadNum);if (nNewSlot == -1)return FALSE;// 保存找到的元素e = m_pElements[nNewSlot].m_element;// 标记为已读m_pElements[nNewSlot].m_nStamp = 0;return TRUE;}


3.添加字符串函数

void AddText(HWND hWndLB, PCTSTR pszFormat, ...){va_list argList;va_start(argList, pszFormat);TCHAR sz[20 * 1024];_vstprintf_s(sz, _countof(sz), pszFormat, argList);ListBox_SetCurSel(hWndLB, ListBox_AddString(hWndLB, sz));va_end(argList);}

void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify){switch (id){case IDCANCEL:EndDialog(hWnd, id);break;case IDC_BTN_STOP:HWND hWndLB = GetDlgItem(g_hWnd, IDC_CLIENT);AddText(hWndLB, TEXT("测试 AddText() 函数, 结束键被按下了..."));Button_Enable(hWndCtl, FALSE);break;}}



4.客户(写者)线程

CQueueg_q(10);volatile LONGg_fShutdown;HWNDg_hWnd;SRWLOCKg_srwLock;CONDITION_VARIABLEg_cvReadyToConsume;// 写者发出信号,表示可以读取CONDITION_VARIABLEg_cvReadyToProduce;// 读者发出信号,表示可以写入// Handles to all reader/writer threadsHANDLE g_hThreads[MAXIMUM_WAIT_OBJECTS];// Number of reader/writer threads  int    g_nNumThreads = 0;

DWORD WINAPI WriterThread(PVOID pvParam){int nThreadNum = PtrToUlong(pvParam);// 线程号HWND hWndLB = GetDlgItem(g_hWnd, IDC_CLIENT);int nRequestNum = 1;// 请求号while (1){CQueue::ELEMENT e = { nThreadNum, nRequestNum };// 以独占模式获得SRWLock,若锁已被别的线程占用,无论占用锁的线程是服务器线程还是// 客户线程,当前线程都会被阻塞在AcquireSRWLockExclusive中,直到锁被释放AcquireSRWLockExclusive(&g_srwLock);if (g_q.IsFull())// 队列已满{AddText(hWndLB, TEXT("线程[%d] 队列已满: 不能添加元素%d"), nThreadNum, nRequestNum);// 睡眠,等待读取者线程读取一个元素,腾出一个位置容纳新元素SleepConditionVariableSRW(&g_cvReadyToProduce, &g_srwLock, INFINITE, 0);}// 添加元素g_q.AddElement(e);AddText(hWndLB, TEXT("线程[%d] 添加元素%d"), nThreadNum, nRequestNum);// 释放锁ReleaseSRWLockExclusive(&g_srwLock);// 通知服务器线程有数据需要处理WakeAllConditionVariable(&g_cvReadyToConsume);Sleep(1500);nRequestNum++;}AddText(hWndLB, TEXT("[%d] bye bye"), nThreadNum);return 0;}

// This macro function calls the C runtime's _beginthreadex function. // The C runtime library doesn't want to have any reliance on Windows' data // types such as HANDLE. This means that a Windows programmer needs to cast// values when using _beginthreadex. Since this is terribly inconvenient, // I created this macro to perform the casting.typedef unsigned (__stdcall *PTHREAD_START) (void *);#define chBEGINTHREADEX(psa, cbStackSize, pfnStartAddr, \   pvParam, dwCreateFlags, pdwThreadId)                 \      ((HANDLE)_beginthreadex(                          \         (void *)        (psa),                         \         (unsigned)      (cbStackSize),                 \         (PTHREAD_START) (pfnStartAddr),                \         (void *)        (pvParam),                     \         (unsigned)      (dwCreateFlags),               \         (unsigned *)    (pdwThreadId)))

BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam){chSETDLGICONS(hWnd, IDI_QUEUE);g_hWnd = hWnd;// 初始化SRWLock   InitializeSRWLock(&g_srwLock);   // 初始化条件变量   InitializeConditionVariable(&g_cvReadyToConsume);   InitializeConditionVariable(&g_cvReadyToProduce);   g_fShutdown = FALSE;   // 创建写者线程   DWORD dwThreadID;   for (int i = 0; i < 4; i++)   g_hThreads[g_nNumThreads++] = chBEGINTHREADEX(NULL, 0, WriterThread, (PVOID)(INT_PTR) i, 0, &dwThreadID);return TRUE;}



5.服务器(读者)线程

BOOL ConsumeElement(int nThreadNum, int nRequestNum, HWND hWndLB){// 以共享模式获得srwLock,如果锁已经被客户端线程以独占模式占用,函数会阻塞// 如果锁已经被另一个服务器线程以共享模式获得,运行对请求进行处理AcquireSRWLockShared(&g_srwLock);// 队列不存在该服务器线程处理的元素,则阻塞,等待一个客户线程产生新的元素// 而触发g_cvReadyToConsume条件变量为止while (g_q.IsEmpty(nThreadNum)){AddText(hWndLB, TEXT("服务器线程[%d] 没有元素可处理"), nThreadNum);SleepConditionVariableSRW(&g_cvReadyToConsume, &g_srwLock, INFINITE, CONDITION_VARIABLE_LOCKMODE_SHARED);}// 获取新元素CQueue::ELEMENT e;g_q.GetNewElement(nThreadNum, e);// 释放锁ReleaseSRWLockShared(&g_srwLock);AddText(hWndLB, TEXT("服务器线程[%d]处理客户线程%d生产的元素%d"), nThreadNum, e.m_nThreadNum, e.m_nRequestNum);// 读取一个元素完成,新增一个空位,通知客户端线程可以继续生产WakeConditionVariable(&g_cvReadyToProduce);return TRUE;}DWORD WINAPI ReaderThread(PVOID pvParam){int nThreadNum = PtrToUlong(pvParam);HWND hWndLB = GetDlgItem(g_hWnd, IDC_SERVER);int nRequestNum = 1;while (1){if (!ConsumeElement(nThreadNum, nRequestNum, hWndLB))return 0;Sleep(2500);}AddText(hWndLB, TEXT("服务器线程[%d] 结束"), nThreadNum);return 0;}

BOOL Dlg_OnInitDialog(HWND hWnd, HWND hWndFocus, LPARAM lParam){chSETDLGICONS(hWnd, IDI_QUEUE);g_hWnd = hWnd;// 初始化SRWLock   InitializeSRWLock(&g_srwLock);   // 初始化条件变量   InitializeConditionVariable(&g_cvReadyToConsume);   InitializeConditionVariable(&g_cvReadyToProduce);   g_fShutdown = FALSE;   // 创建写者线程   DWORD dwThreadID;   for (int i = 0; i < 4; i++)   g_hThreads[g_nNumThreads++] = chBEGINTHREADEX(NULL, 0, WriterThread, (PVOID)(INT_PTR) i, 0, &dwThreadID);   // 创建读者线程   for (int i = 0; i < 2; i++)g_hThreads[g_nNumThreads++] = chBEGINTHREADEX(NULL, 0, ReaderThread, (PVOID)(INT_PTR) i, 0, &dwThreadID);return TRUE;}


6.停止处理

DWORD WINAPI WriterThread(PVOID pvParam){int nThreadNum = PtrToUlong(pvParam);// 线程号HWND hWndLB = GetDlgItem(g_hWnd, IDC_CLIENT);for (int nRequestNum = 1; !g_fShutdown; nRequestNum++){CQueue::ELEMENT e = { nThreadNum, nRequestNum };// 以独占模式获得SRWLock,若锁已被别的线程占用,无论占用锁的线程是服务器线程还是// 客户线程,当前线程都会被阻塞在AcquireSRWLockExclusive中,直到锁被释放AcquireSRWLockExclusive(&g_srwLock);if (g_q.IsFull())// 队列已满{AddText(hWndLB, TEXT("客户线程[%d]队列已满:不能添加元素%d"), nThreadNum, nRequestNum);// 睡眠,等待读取者线程读取一个元素,腾出一个位置容纳新元素SleepConditionVariableSRW(&g_cvReadyToProduce, &g_srwLock, INFINITE, 0);}if (g_fShutdown){AddText(hWndLB, TEXT("客户线程[%d] 结束"), nThreadNum);// 释放锁ReleaseSRWLockExclusive(&g_srwLock);// 告诉所有阻塞的线程结束WakeAllConditionVariable(&g_cvReadyToProduce);return 0;}else{// 添加元素g_q.AddElement(e);AddText(hWndLB, TEXT("客户线程[%d]添加元素%d"), nThreadNum, nRequestNum);// 释放锁ReleaseSRWLockExclusive(&g_srwLock);// 通知服务器线程有数据需要处理WakeAllConditionVariable(&g_cvReadyToConsume);Sleep(1500);}}AddText(hWndLB, TEXT("客户线程[%d] 结束"), nThreadNum);return 0;}BOOL ConsumeElement(int nThreadNum, int nRequestNum, HWND hWndLB){// 以共享模式获得srwLock,如果锁已经被客户端线程以独占模式占用,函数会阻塞// 如果锁已经被另一个服务器线程以共享模式获得,运行对请求进行处理AcquireSRWLockShared(&g_srwLock);// 队列不存在该服务器线程处理的元素,则阻塞,等待一个客户线程产生新的元素// 而触发g_cvReadyToConsume条件变量为止while (g_q.IsEmpty(nThreadNum)){AddText(hWndLB, TEXT("服务器线程[%d] 没有元素可处理"), nThreadNum);SleepConditionVariableSRW(&g_cvReadyToConsume, &g_srwLock, INFINITE, CONDITION_VARIABLE_LOCKMODE_SHARED);}if (g_fShutdown){AddText(hWndLB, TEXT("服务器线程[%d] 结束"), nThreadNum);// 释放锁ReleaseSRWLockShared(&g_srwLock);// 通知另一个服务器线程结束WakeConditionVariable(&g_cvReadyToConsume);return FALSE;}// 获取新元素CQueue::ELEMENT e;g_q.GetNewElement(nThreadNum, e);// 释放锁ReleaseSRWLockShared(&g_srwLock);AddText(hWndLB, TEXT("服务器线程[%d]处理客户线程%d生产的元素%d"), nThreadNum, e.m_nThreadNum, e.m_nRequestNum);// 读取一个元素完成,新增一个空位,通知客户端线程可以继续生产WakeConditionVariable(&g_cvReadyToProduce);return TRUE;}DWORD WINAPI ReaderThread(PVOID pvParam){int nThreadNum = PtrToUlong(pvParam);HWND hWndLB = GetDlgItem(g_hWnd, IDC_SERVER);for (int nRequestNum = 1; !g_fShutdown; nRequestNum++){if (!ConsumeElement(nThreadNum, nRequestNum, hWndLB))return 0;Sleep(2500);}AddText(hWndLB, TEXT("服务器线程[%d] 结束"), nThreadNum);return 0;}

void StopProcessing(){if (!g_fShutdown){// 告诉所有线程结束InterlockedExchange(&g_fShutdown, TRUE);// 释放所有在等待条件变量的线程WakeAllConditionVariable(&g_cvReadyToConsume);WakeAllConditionVariable(&g_cvReadyToProduce);// 等待所有线程关闭,然后清理WaitForMultipleObjects(g_nNumThreads, g_hThreads, TRUE, INFINITE);// 清理内核对象while (g_nNumThreads--)CloseHandle(g_hThreads[g_nNumThreads]);AddText(GetDlgItem(g_hWnd, IDC_SERVER), TEXT("----------------"));AddText(GetDlgItem(g_hWnd, IDC_CLIENT), TEXT("----------------"));}}DWORD WINAPI StoppingThread(PVOID pvParam){StopProcessing();return 0;}void Dlg_OnCommand(HWND hWnd, int id, HWND hWndCtl, UINT codeNotify){switch (id){case IDCANCEL:EndDialog(hWnd, id);break;case IDC_BTN_STOP:// 结束处理不能在UI线程中调用,否则导致死锁DWORD dwThreadID;CloseHandle(chBEGINTHREADEX(NULL, 0, StoppingThread, NULL, 0, &dwThreadID));Button_Enable(hWndCtl, FALSE);break;}}INT_PTR WINAPI Dlg_Proc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam){switch (uMsg){chHANDLE_DLGMSG(hWnd, WM_INITDIALOG, Dlg_OnInitDialog);chHANDLE_DLGMSG(hWnd, WM_COMMAND, Dlg_OnCommand);}return FALSE;}/*************************************************************************/int WINAPI _tWinMain(HINSTANCE hInstExe, HINSTANCE, PTSTR pszCmdLine, int){DialogBox(hInstExe, MAKEINTRESOURCE(IDD_QUEUE), NULL, Dlg_Proc);StopProcessing();return 0;}


原创粉丝点击