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

更多讨论
相关文章
|
10月前
|
监控 Linux 测试技术
C++零拷贝网络编程实战:从理论到生产环境的性能优化之路
🌟 蒋星熠Jaxonic,技术宇宙中的星际旅人。深耕C++与零拷贝网络编程,从sendfile到DPDK,实战优化服务器性能,毫秒级响应、CPU降60%。分享架构思维,共探代码星辰大海!
|
程序员 编译器 C++
【实战指南】C++ lambda表达式使用总结
Lambda表达式是C++11引入的特性,简洁灵活,可作为匿名函数使用,支持捕获变量,提升代码可读性与开发效率。本文详解其基本用法与捕获机制。
441 88
|
C语言 C++
【实战指南】 C/C++ 枚举转字符串实现
本文介绍了在C/C++中实现枚举转字符串的实用技巧,通过宏定义与统一管理枚举名,提升代码调试效率并减少维护错误。
662 97
|
存储 SQL 安全
Java 无锁方式实现高性能线程实战操作指南
本文深入探讨了现代高并发Java应用中单例模式的实现方式,分析了传统单例(如DCL)的局限性,并提出了多种无锁实现方案。包括基于ThreadLocal的延迟初始化、VarHandle原子操作、Record不可变对象、响应式编程(Reactor)以及CDI依赖注入等实现方式。每种方案均附有代码示例及适用场景,同时通过JMH性能测试对比各实现的优劣。最后,结合实际案例设计了一个高性能配置中心,展示了无锁单例在实际开发中的应用。总结中提出根据场景选择合适的实现方式,并遵循现代单例设计原则以优化性能和安全性。文中还提供了代码获取链接,便于读者实践与学习。
281 0
|
设计模式 运维 监控
并发设计模式实战系列(4):线程池
需要建立持续的性能剖析(Profiling)和调优机制。通过以上十二个维度的系统化扩展,构建了一个从。设置合理队列容量/拒绝策略。动态扩容/优化任务处理速度。检查线程栈定位热点代码。调整最大用户进程数限制。CPU占用率100%
733 0
|
监控 Linux C++
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展(2)
本文是《4步实现C++插件化编程》的延伸,重点介绍了新增的插件“热拔插”功能。通过`inotify`接口监控指定路径下的文件变动,结合`epoll`实现非阻塞监听,动态加载或卸载插件。核心设计包括`SprDirWatch`工具类封装`inotify`,以及`PluginManager`管理插件生命周期。验证部分展示了插件加载与卸载的日志及模块状态,确保功能稳定可靠。优化过程中解决了动态链接库句柄泄露问题,强调了采纳用户建议的重要性。
623 110
【实战指南】4步实现C++插件化编程,轻松实现功能定制与扩展(2)
|
10月前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
819 0
|
人工智能 程序员 C++
【实战经验】C/C++右移高位补0还是1?
本文探讨了C/C++中右移运算时高位补0还是补1的问题。通过示例代码分析,揭示了右移规则:无符号类型高位补0;有符号类型根据正负决定(正数补0,负数补1)。文中列举了可能导致错误的场景,并提供了两种规避措施——使用无符号类型和掩码校正,确保结果符合预期。最后总结指出,右移运算虽常见,但若处理不当易引发隐晦Bug,需谨慎对待。
865 98
|
12月前
|
数据采集 消息中间件 并行计算
Python多线程与多进程性能对比:从原理到实战的深度解析
在Python编程中,多线程与多进程是提升并发性能的关键手段。本文通过实验数据、代码示例和通俗比喻,深入解析两者在不同任务类型下的性能表现,帮助开发者科学选择并发策略,优化程序效率。
840 1

热门文章

最新文章