閱讀317 返回首頁    go 阿裏雲 go 技術社區[雲棲]


《C++多線程編程實戰》——2.9 在用戶空間實現線程

本節書摘來自異步社區出版社《C++多線程編程實戰》一書中的第2章,第2.9節,作者: 【黑山共和國】Milos Ljumovic(米洛斯 留莫維奇),更多章節內容可以訪問雲棲社區“異步社區”公眾號查看。

2.9 在用戶空間實現線程

既可以在用戶空間也可以在內核中實現線程包。具體選擇在哪裏實現還存在一些爭議,在一些實現中可能會混合使用內核線程和用戶線程。

我們將討論在不同地方實現線程包的方法及優缺點。第1種方法是,把整個線程包放進用戶空間,內核完全不知道。就內核而言,它管理著普通的單線程進程。這種方法的優點和最顯著的優勢是,可以在不支持線程的操作係統中實現用戶級線程包。

過去,傳統的操作係統就采用這種方法,甚至沿用至今。用這種方法,線程可以通過庫來實現。所有這些實現都具有相同的通用結構。線程運行在運行時係統的頂部,該係統是專門管理線程的過程集合。我們在前麵見過一些例子(CreateThreadTerminateThread等),以後還會見到更多。

下麵的程序示例演示了在用戶空間中的線程用法。我們要複製大型文件,但是不想一開始就讀取整個文件的內容,或者更優化地一部分一部分地讀取,而且不用在文件中寫入數據。這就涉及2.5節中提到的生產者-消費者問題。

準備就緒
確定安裝並運行了Visual Studio。

操作步驟
1. 創建一個新的Win32應用程序項目,並命名為ConcurrentFileCopy

2. 打開【解決方案資源管理器】,添加一個新的頭文件,命名為ConcurrentFileCopy.h。打開ConcurrentFileCopy.h,並輸入下麵的代碼:

#pragma once

#include <windows.h>
#include <commctrl.h>
#include <memory.h>
#include <tchar.h>
#include <math.h>

#pragma comment ( lib, "comctl32.lib" )
#pragma comment ( linker, "\"/manifestdependency:type='win32' \
name='Microsoft.Windows.Common-Controls' \
version='6.0.0.0' processorArchitecture='*' \
publicKeyToken='6595b64144ccf1df' language='*'\"" )

ATOM RegisterWndClass(HINSTANCE hInstance);
HWND InitializeInstance(HINSTANCE hInstance, int nCmdShow, HWND& hWndPB);
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
DWORD WINAPI ReadRoutine(LPVOID lpParameter);
DWORD WINAPI WriteRoutine(LPVOID lpParameter);
BOOL FileDialog(HWND hWnd, LPTSTR szFileName, DWORD
  dwFileOperation);
DWORD GetBlockSize(DWORD dwFileSize);

#define BUTTON_CLOSE 100
#define FILE_SAVE 0x0001
#define FILE_OPEN 0x0002
#define MUTEX_NAME _T("__RW_MUTEX__")

typedef struct _tagCOPYDETAILS
{
  HINSTANCE hInstance;
  HWND hWndPB;
  LPTSTR szReadFileName;
  LPTSTR szWriteFileName;
} COPYDETAILS, *PCOPYDETAILS;```
3.現在,打開【解決方案資源管理器】,並添加一個新的源文件,命名為`ConcurrentFileCopy.cpp`。打開`ConcurrentFileCopy.c`,並輸入下麵的代碼:

include "ConcurrentFileCopy.h"

TCHAR* szTitle = T("Concurrent file copy");
TCHAR* szWindowClass = _T("
CFC_WND_CLASS _");

DWORD dwReadBytes = 0;
DWORD dwWriteBytes = 0;

DWORD dwBlockSize = 0;
DWORD dwFileSize = 0;

HLOCAL pMemory = NULL;

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrev, LPTSTR
  szCmdLine, int iCmdShow)
{
  UNREFERENCED_PARAMETER(hPrev);
  UNREFERENCED_PARAMETER(szCmdLine);

  RegisterWndClass(hInstance);

  HWND hWnd = NULL;
  HWND hWndPB = NULL;

  if (!(hWnd = InitializeInstance(hInstance, iCmdShow, hWndPB)))
  {
    return 1;
  }

  MSG msg = { 0 };
  TCHAR szReadFile[MAX_PATH];
  TCHAR szWriteFile[MAX_PATH];

  if (FileDialog(hWnd, szReadFile, FILE_OPEN) && FileDialog(hWnd,
    szWriteFile, FILE_SAVE))
  {
    COPYDETAILS copyDetails = { hInstance, hWndPB, szReadFile,
      szWriteFile };
    HANDLE hMutex = CreateMutex(NULL, FALSE, MUTEX_NAME);
    HANDLE hReadThread = CreateThread(NULL, 0,
      (LPTHREAD_START_ROUTINE)ReadRoutine, &copyDetails, 0, NULL);
    while (GetMessage(&msg, NULL, 0, 0))
    {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }
    CloseHandle(hReadThread);
    CloseHandle(hMutex);
  }
  else
  {
    MessageBox(hWnd, _T("Cannot open file!"),
      _T("Error!"), MB_OK);
  }
  LocalFree(pMemory);
  UnregisterClass(szWindowClass, hInstance);
  return (int)msg.wParam;
}

ATOM RegisterWndClass(HINSTANCE hInstance)
{
  WNDCLASSEX wndEx;

  wndEx.cbSize = sizeof(WNDCLASSEX);
  wndEx.style = CS_HREDRAW | CS_VREDRAW;
  wndEx.lpfnWndProc = WndProc;
  wndEx.cbClsExtra = 0;
  wndEx.cbWndExtra = 0;
  wndEx.hInstance = hInstance;
  wndEx.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION));
  wndEx.hCursor = LoadCursor(NULL, IDC_ARROW);
  wndEx.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
  wndEx.lpszMenuName = NULL;
  wndEx.lpszClassName = szWindowClass;
  wndEx.hIconSm = LoadIcon(wndEx.hInstance, MAKEINTRESOURCE(IDI_APPLICATION));

  return RegisterClassEx(&wndEx);
}

HWND InitializeInstance(HINSTANCE hInstance, int iCmdShow, HWND& hWndPB)
{
  HWND hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPED
    | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 200, 200, 440, 290,
    NULL, NULL, hInstance, NULL);
  RECT rcClient = { 0 };
  int cyVScroll = 0;

  if (!hWnd)
  {
    return NULL;
  }

  HFONT hFont = CreateFont(14, 0, 0, 0, FW_NORMAL, FALSE, FALSE,
    FALSE, BALTIC_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
    DEFAULT_QUALITY, DEFAULT_PITCH | FF_MODERN,
    _T("Microsoft Sans Serif"));
  HWND hButton = CreateWindow(_T("BUTTON"), _T("Close"), WS_CHILD
    | WS_VISIBLE | BS_PUSHBUTTON | WS_TABSTOP, 310, 200, 100, 25,
    hWnd, (HMENU)BUTTON_CLOSE, hInstance, NULL);
  SendMessage(hButton, WM_SETFONT, (WPARAM)hFont, TRUE);

  GetClientRect(hWnd, &rcClient);
  cyVScroll = GetSystemMetrics(SM_CYVSCROLL);

  hWndPB = CreateWindow(PROGRESS_CLASS, (LPTSTR)NULL, WS_CHILD |
    WS_VISIBLE, rcClient.left, rcClient.bottom - cyVScroll,
    rcClient.right, cyVScroll, hWnd, (HMENU)0, hInstance, NULL);
  SendMessage(hWndPB, PBM_SETSTEP, (WPARAM)1, 0);

  ShowWindow(hWnd, iCmdShow);
  UpdateWindow(hWnd);

  return hWnd;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
  switch (uMsg)
  {
    case WM_COMMAND:
    {
      switch (LOWORD(wParam))
      {
        case BUTTON_CLOSE:
        {
          DestroyWindow(hWnd);
          break;
        }
      }
      break;
    }
    case WM_DESTROY:
    {
      PostQuitMessage(0);
      break;
    }
    default:
    {
      return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
  }
  return 0;
}

DWORD WINAPI ReadRoutine(LPVOID lpParameter)
{
  PCOPYDETAILS pCopyDetails = (PCOPYDETAILS)lpParameter;
  HANDLE hFile = CreateFile(pCopyDetails->szReadFileName,
    GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL, NULL);
  if (hFile == (HANDLE)INVALID_HANDLE_VALUE)
  {
    return FALSE;
  }
  dwFileSize = GetFileSize(hFile, NULL);
  dwBlockSize = GetBlockSize(dwFileSize);
  HANDLE hWriteThread = CreateThread(NULL, 0,
    (LPTHREAD_START_ROUTINE)WriteRoutine, pCopyDetails, 0, NULL);
  size_t uBufferLength = (size_t)ceil((double) dwFileSize / (double)dwBlockSize);
  SendMessage(pCopyDetails->hWndPB, PBM_SETRANGE, 0,
    MAKELPARAM(0, uBufferLength));
  pMemory = LocalAlloc(LPTR, dwFileSize);
  void* pBuffer = LocalAlloc(LPTR, dwBlockSize);

  int iOffset = 0;
  DWORD dwBytesRed = 0;

  do
  {
    ReadFile(hFile, pBuffer, dwBlockSize, &dwBytesRed, NULL);
    if (!dwBytesRed)
    {
      break;
    }
    HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE,
      MUTEX_NAME);
    WaitForSingleObject(hMutex, INFINITE);
    memcpy((char*)pMemory + iOffset, pBuffer, dwBytesRed);
    dwReadBytes += dwBytesRed;

    ReleaseMutex(hMutex);

    iOffset += (int)dwBlockSize;
  } while (true);

  LocalFree(pBuffer);
  CloseHandle(hFile);
  CloseHandle(hWriteThread);
  return 0;
}

DWORD WINAPI WriteRoutine(LPVOID lpParameter)
{
  PCOPYDETAILS pCopyDetails = (PCOPYDETAILS)lpParameter;
  HANDLE hFile = CreateFile(pCopyDetails->szWriteFileName,
    GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  if (hFile == (HANDLE)INVALID_HANDLE_VALUE)
  {
    return FALSE;
  }

  DWORD dwBytesWritten = 0;
  int iOffset = 0;

  do
  {
    int iRemainingBytes = (int)dwFileSize - iOffset;
    if (iRemainingBytes <= 0)
    {
      break;
    }
    Sleep(10);
    if (dwWriteBytes < dwReadBytes)
    {
      DWORD dwBytesToWrite = dwBlockSize;
      if (!(dwFileSize / dwBlockSize))
      {
        dwBytesToWrite = (DWORD)iRemainingBytes;
      }
      HANDLE hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, MUTEX_NAME);
      WaitForSingleObject(hMutex, INFINITE);

      WriteFile(hFile, (char*)pMemory + iOffset, dwBytesToWrite,
        &dwBytesWritten, NULL);
      dwWriteBytes += dwBytesWritten;

      ReleaseMutex(hMutex);

      SendMessage(pCopyDetails->hWndPB, PBM_STEPIT, 0, 0);
      iOffset += (int)dwBlockSize;
    }
  } while (true);

  CloseHandle(hFile);
  return 0;
}

BOOL FileDialog(HWND hWnd, LPTSTR szFileName, DWORD  dwFileOperation)
{

ifdef _UNICODE

  OPENFILENAMEW ofn;

else

  OPENFILENAMEA ofn;

endif

  TCHAR szFile[MAX_PATH];

  ZeroMemory(&ofn, sizeof(ofn));
  ofn.lStructSize = sizeof(ofn);
  ofn.hwndOwner = hWnd;
  ofn.lpstrFile = szFile;
  ofn.lpstrFile[0] = '\0';
  ofn.nMaxFile = sizeof(szFile);
  ofn.lpstrFilter = _T("All\0*.*\0Text\0*.TXT\0");
  ofn.nFilterIndex = 1;
  ofn.lpstrFileTitle = NULL;
  ofn.nMaxFileTitle = 0;
  ofn.lpstrInitialDir = NULL;
  ofn.Flags = dwFileOperation == FILE_OPEN ? OFN_PATHMUSTEXIST |
  OFN_FILEMUSTEXIST : OFN_SHOWHELP | OFN_OVERWRITEPROMPT;

  if (dwFileOperation == FILE_OPEN)
  {
    if (GetOpenFileName(&ofn) == TRUE)
    {
      _tcscpy_s(szFileName, MAX_PATH - 1, szFile);
      return TRUE;
    }
  }
  else
  {
    if (GetSaveFileName(&ofn) == TRUE)
    {
      _tcscpy_s(szFileName, MAX_PATH - 1, szFile);
      return TRUE;
    }
  }
  return FALSE;
}

DWORD GetBlockSize(DWORD dwFileSize)
{
  return dwFileSize > 4096 ? 4096 : 512;
}``
示例分析
我們創建了一個和哲學家就餐示例非常像的UI。例程
MyRegisterClassInitInstance和WndProc幾乎都一樣。我們在程序中添加FileDialog`來詢問用戶讀寫文件的路徑。為了讀和寫,分別啟動了兩個線程。

操作係統的調度十分複雜。我們根本不知道是調度算法還是硬件中斷使得某線程被調度在CUP中執行。這意味著寫線程可能在讀線程之前執行。出現這種情況會導致一個異常,因為寫線程沒東西可寫。

因此,我們在寫操作中添加了if條件,如下代碼所示:

if ( dwBytesWritten < dwBytesRead ) 
{
  WriteFile(hFile, pCharTmp, sizeof(TCHAR) * BLOCK_SIZE, &dwFileSize, NULL); 
  dwBytesWritten += dwFileSize; 
  SendMessage( hProgress, PBM_STEPIT, 0, 0 ); 
}```
線程在獲得互斥量後,才能執行寫操作。盡管如此,係統仍然有可能在讀線程之前調度寫線程,此時緩衝區是空的。因此,每次讀線程獲得一些內容,就要把讀取的字節數加給dwBytesRed變量,隻有寫線程的字節數小於讀線程的字節數,才可以執行寫操作。否則,本輪循環將跳過寫操作,並釋放互斥量供其他線程使用。

更多討論
生產者-消費者問題也稱為有界緩衝問題。兩個進程共享一個固定大小的公共緩衝區。生產者把信息放入緩衝區,消費者把信息從緩衝區中取出來。該問題也可擴展為m個生產者和n個消費者的問題。不過,這裏我們簡化了問題,隻考慮一個生產者和一個消費者。當生產者往緩衝區放入新項目時緩衝區滿了,就會產生問題。解決的方案是,讓生產者睡眠,並在消費者已經移除一個或多個項時喚醒生產者。同理,如果消費者要從緩衝區取項目時緩衝區為空,也會產生問題。解決的方案是,讓消費者睡眠,等生產者把項目放入緩衝區後再喚醒它。這種方法看起來很簡單,但是會導致競態條件。讀者可以用學過的知識,嚐試解決類似的情況。

最後更新:2017-06-01 13:01:17

  上一篇:go  誕生於納粹集中營裏的黑科技:科塔計算器
  下一篇:go  傳奇數學家拉馬努金留下的數學神諭,解開了多年懸而未決的神秘難題