《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("All0.0Text0*.TXT0");
  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变量,只有写线程的字节数小于读线程的字节数,才可以执行写操作。否则,本轮循环将跳过写操作,并释放互斥量供其他线程使用。

更多讨论
相关文章
|
23天前
|
缓存 安全 C++
C++无锁队列:解锁多线程编程新境界
【10月更文挑战第27天】
36 7
|
23天前
|
消息中间件 存储 安全
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
48 1
C++ 多线程之初识多线程
|
29天前
|
存储 并行计算 安全
C++多线程应用
【10月更文挑战第29天】C++ 中的多线程应用广泛,常见场景包括并行计算、网络编程中的并发服务器和图形用户界面(GUI)应用。通过多线程可以显著提升计算速度和响应能力。示例代码展示了如何使用 `pthread` 库创建和管理线程。注意事项包括数据同步与互斥、线程间通信和线程安全的类设计,以确保程序的正确性和稳定性。
|
28天前
|
自然语言处理 编译器 Linux
告别头文件,编译效率提升 42%!C++ Modules 实战解析 | 干货推荐
本文中,阿里云智能集团开发工程师李泽政以 Alinux 为操作环境,讲解模块相比传统头文件有哪些优势,并通过若干个例子,学习如何组织一个 C++ 模块工程并使用模块封装第三方库或是改造现有的项目。
|
30天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
20 3
|
30天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
19 2
|
30天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
30 2
|
1月前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
137 5
|
30天前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
25 1

相关实验场景

更多
下一篇
无影云桌面