《C++多线程编程实战》——2.9 在用户空间实现线程

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

更多讨论
时间: 2024-10-23 18:25:50

《C++多线程编程实战》——2.9 在用户空间实现线程的相关文章

《C++多线程编程实战》——第2章 进程和线程的概念2.1 简介

第2章 进程和线程的概念 C++多线程编程实战本章介绍以下内容: 进程和线程解释进程模型进程的实现进程间通信(IPC)解决典型的IPC问题线程模型的实现线程的用法在用户空间实现线程在内核实现线程 2.1 简介 现在的计算机能同时处理多件事,许多Windows用户还没有完全意识到这一点.我们举例说明一下.当启动PC系统时,许多进程都在后台启动(例如,管理电子邮件的进程.负责更新病毒库的进程等).通常,用户在执行其他任务时(如,上网),还会打印文件或播放CD.这些活动都需要管理.支持多进程的多任务系

《C++多线程编程实战》——2.2 进程和线程

2.2 进程和线程 图2.1演示了执行4个程序调度的一个单核CPU多任务处理系统.图2.2演示了执行4个进程的一个多核CPU多任务处理系统,每个进程单独运行,各有一个控制流. 图2.1 单核CPU多任务处理系统 图2.2 多核CPU多任务处理系统 如图2.3所示,随着时间的推移,虽然进程有不同程度的进展,但是在每一时刻,单核CPU只运行一个进程. 图2.3 进程在单核CPU中的运行情况 前面提到过,在传统的操作系统中,每个进程都有一个地址空间和一个控制线程.在许多情况下,一个进程的地址空间中要执

《C++多线程编程实战》——第1章 C++概念和特性简介1.1 介绍

第1章 C++概念和特性简介 C++多线程编程实战 本章介绍以下内容: 创建一个C++项目 程序结构.执行流.运行时对象 结构编程方法 理解面向对象编程方法 解释继承.重载和覆盖 理解多态 事件处理器和消息传递接口 链表.队列.栈示例 1.1 介绍 系统所执行的程序的进程或抽象是所有操作系统的核心概念.现在,绝大多数的操作系统在同一时间内都可以进行多项操作.例如,计算机在用户编辑Word文档时,还可以打印该文档.从硬盘缓冲区读数据.播放音乐等.在多任务操作系统中,中央处理单元(CPU)在程序中快

《JAVA多线程编程实战指南》之Two-phase Termination(两阶段终止)模式

本文是<JAVA多线程编程实战指南>的样章,感谢作者授权并发网(ifeve.com)发表此文.感谢demochen整理此文. 5.1Two-phase Termination模式简介 停止线程是一个目标简单而实现却不那么简单的任务.首先,Java没有提供直接的API用于停止线程.此外,停止线程还有一些额外的细节需要考虑,如停止的线程处于阻塞(如等待锁)或者等待状态(等待其他线程),尚有未处理完的任务等. Two-phase Termination模式通过将停止线程这个动作分解为准备阶段和执行阶

《C++多线程编程实战》导读

前言 C++多线程编程实战多线程编程正逐渐成为IT行业和开发人员关注的焦点.开发商希望开发出用户友好.界面丰富,而且能并发执行的应用程序.强大的C++语言和本地Win32 API特性为多线程编程提供了良好开端.有了强大的C++,可以轻松地创建不同类型的应用程序,执行并行,而且还能优化现有的工作. 本书是一本实践为主.通俗易懂的Windows多线程编程指导.你将学到如何从多线程方案中受益,增强你的开发能力,构建更好的应用程序.本书不仅讲解了创建并行代码时遇到的问题,而且还帮助读者详细理解同步技术.

Java多线程编程实战之不提倡的方法

不提倡使用的方法是为支持向后兼容性而保留的那些方法,它们在以后的版本中可能出现,也可能不出现.Java 多线程支持在版本 1.1 和版本 1.2 中做了重大修订,stop().suspend() 和 resume() 函数已不提倡使用.这些函数在 JVM 中可能引入微妙的错误.虽然函数名可能听起来很诱人,但请抵制诱惑不要使用它们. 调试线程化的程序 在线程化的程序中,可能发生的某些常见而讨厌的情况是死锁.活锁.内存损坏和资源耗尽. 死锁 死锁可能是多线程程序最常见的问题.当一个线程需要一个资源而

Java多线程编程实战之限制优先级

限制线程优先级和调度 Java 线程模型涉及可以动态更改的线程优先级.本质上,线程的优先级是从 1 到 10 之间的一个数字,数字越大表明任务越紧急.JVM 标准首先调用优先级较高的线程,然后才调用优先级较低的线程.但是,该标准对具有相同优先级的线程的处理是随机的.如何处理这些线程取决于基层的操作系统策略.在某些情况下,优先级相同的线程分时运行:在另一些情况下,线程将一直运行到结束.请记住,Java 支持 10 个优先级,基层操作系统支持的优先级可能要少得多,这样会造成一些混乱.因此,只能将优先

《C++多线程编程实战》——2.10 在内核实现线程

2.10 在内核实现线程 整个内核就是一个进程,许多系统(内核)线程在其上下文中运行.内核有一个线程表,跟踪该系统中所有的线程. 内核维护这个传统的进程表以跟踪进程.那些可以阻塞线程的函数调用可作为系统调用执行,这比执行系统过程的代价更高.当线程被阻塞时,内核必须运行其他线程.当线程被毁坏时,则被标记为不可运行.但是,它的内核数据结构不会受到影响.然后在创建新的线程时,旧的线程将被再次激活,回收资源以备后用.当然,也可以回收用户级线程,但如果线程管理开销非常小,就没必要这样做. 准备就绪下面的示

Python多线程编程(八):使用Event实现线程间通信_python

使用threading.Event可以实现线程间相互通信,之前的Python:使用threading模块实现多线程编程七[使用Condition实现复杂同步]我们已经初步实现了线程间通信的基本功能,但是更为通用的一种做法是使用threading.Event对象.使用threading.Event可以使一个线程等待其他线程的通知,我们把这个Event传递到线程对象中,Event默认内置了一个标志,初始值为False.一旦该线程通过wait()方法进入等待状态,直到另一个线程调用该Event的set