《C++多线程编程实战》——.7 线程模型的实现

简介:

本节书摘来自异步社区出版社《C++多线程编程实战》一书中的第2章,第2.7节,作者: 【黑山共和国】Milos Ljumovic(米洛斯 留莫维奇),更多章节内容可以访问云栖社区“异步社区”公众号查看。

2.7 线程模型的实现

我们可以把进程看作是一个对象,它的任务就是把相关资源分组。每个进程都有一个地址空间,如图2.10所示。


719099e750970c937b3d080f5d98e0b3456f3a96

图2.10 进程的地址空间

这个所谓的进程图像必须在初始化CreateProcess时加载至物理内存中。所有的资源(如文件句柄、子进程的信息、信号处理器等)都被储存起来。把它们以进程的形式分组在一起,更容易管理。

除进程外,还有一个重要的概念是线程。线程是CPU可执行调度的最小单位。也就是说,进程本身不能获得CPU时间,只有它的线程才可以。线程通过它的工作变量和栈来储存CPU寄存器的信息。栈包含与函数调用相关的数据,在每个函数被调用但尚未返回时,为其创建一个框架。线程可以在CPU上执行,而进程则不行。但是,进程至少必须有一个线程,通常把这个线程称为主线程。因此,当我们说在CPU上执行的进程时,指的是进程中的主线程。

进程用于分组资源,线程是在CPU上调度执行的实体。在同一个进程环境中可以执行多个线程,理解这点很重要。多线程并行运行在一个进程上下文,与在一个计算机中并行运行的多个进程相同。术语“多线程”指的是在单进程上下文中运行的多线程。

如图2.11所示,有3个进程,每个进程中都有一个线程。


46b0d827982b14721825f81e820ca0b222dc3230

图2.11 3个进程中各有1个线程

图2.12演示了一个有3个线程的进程。虽然这两种情况中都有3个线程,但是在图2.11中,每个线程都在不同的地址空间中运行,而图2.12中的3个线程共享同一个地址空间。


820665a5919336e9e274f37acfaed7d72a3c2e2e

图2.12 有3个线程的进程

在单核CPU系统中运行多线程的进程时,各线程轮流运行。系统通过快速切换多个进程,营造并行处理的假象。多线程也以这样的方式运行。一个有3个线程的进程,其各线程表现为并行运行。单核CPU每次运行一个线程,花费CUP调度处理该进程时间的1/3(大概是这样,CPU时间取决于操作系统、调度算法等)。在多处理器系统中,情况类似。只有单核CPU执行线程时才与本书描述的方式相同。多核的好处是,可以并行运行更多的线程,充分发挥本地硬件的并行处理能力和多线程的执行能力。

下面的例子用两个线程实现一个简单的数组排序,演示了线程的基本用法。

准备就绪
确定安装并运行了Visual Studio。

操作步骤
1.创建一个新的默认Win32控制台应用程序,名为MultithreadedArraySort

2.打开MultithreadedArraySort.cpp,并输入下面的代码:

#include "stdafx.h"
#include <Windows.h>
#include <iostream>
#include <tchar.h>

using namespace std;

#define THREADS_NUMBER 2
#define ELEMENTS_NUMBER 200
#define BLOCK_SIZE ELEMENTS_NUMBER / THREADS_NUMBER
#define MAX_VALUE 1000

typedef struct _tagARRAYOBJECT
{
  int* iArray;
  int iSize;
  int iThreadID;
} ARRAYOBJECT, *PARRAYOBJECT;

DWORD WINAPI ThreadStart(LPVOID lpParameter);
void PrintArray(int* iArray, int iSize);
void MergeArrays(int* leftArray, int leftArrayLenght, int*
  rightArray, int rightArrayLenght, int* mergedArray);

int _tmain(int argc, TCHAR* argv[])
{
  int iArray1[BLOCK_SIZE];
  int iArray2[BLOCK_SIZE];
  int iArray[ELEMENTS_NUMBER];
  for (int iIndex = 0; iIndex < BLOCK_SIZE; iIndex++)
  {
    iArray1[iIndex] = rand() % MAX_VALUE;
    iArray2[iIndex] = rand() % MAX_VALUE;
  }

  HANDLE hThreads[THREADS_NUMBER];
  ARRAYOBJECT pObject1 = { &(iArray1[0]), BLOCK_SIZE, 0 };
  hThreads[0] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)
    ThreadStart, (LPVOID)&pObject1, 0, NULL);

  ARRAYOBJECT pObject2 = { &(iArray2[0]), BLOCK_SIZE, 1 };
  hThreads[1] = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)
    ThreadStart, (LPVOID)&pObject2, 0, NULL);

  cout << "Waiting execution..." << endl;
  WaitForMultipleObjects(THREADS_NUMBER, hThreads, TRUE, INFINITE);

  MergeArrays(&iArray1[0], BLOCK_SIZE, &iArray2[0], BLOCK_SIZE, &iArray[0]);
  PrintArray(iArray, ELEMENTS_NUMBER);

  CloseHandle(hThreads[0]);
  CloseHandle(hThreads[1]);

  cout << "Array sorted..." << endl;
  return 0;
}

DWORD WINAPI ThreadStart(LPVOID lpParameter)
{
  PARRAYOBJECT pObject = (PARRAYOBJECT)lpParameter;
  int iTmp = 0;
  for (int iIndex = 0; iIndex < pObject->iSize; iIndex++)
  {
    for (int iEndIndex = pObject->iSize - 1; iEndIndex > iIndex; iEndIndex--)
    {
      if (pObject->iArray[iEndIndex] < pObject->iArray[iIndex])
      {
        iTmp = pObject->iArray[iEndIndex];
        pObject->iArray[iEndIndex] = pObject->iArray[iIndex];
        pObject->iArray[iIndex] = iTmp;
      }
    }
  }
  return 0;
}

void PrintArray(int* iArray, int iSize)
{
  for (int iIndex = 0; iIndex < iSize; iIndex++)
  {
    cout << " " << iArray[iIndex];
  }
  cout << endl;
}

void MergeArrays(int* leftArray, int leftArrayLenght, int*
  rightArray, int rightArrayLenght, int* mergedArray)
{
  int i = 0;
  int j = 0;
  int k = 0;
  while (i < leftArrayLenght && j < rightArrayLenght)
  {
    if (leftArray[i] < rightArray[j])
    {
      mergedArray[k] = leftArray[i];
      i++;
    }
    else
    {
      mergedArray[k] = rightArray[j];
      j++;
    }
    k++;
  }
  if (i >= leftArrayLenght)
  {
    while (j < rightArrayLenght)
    {
      mergedArray[k] = rightArray[j];
      j++;
      k++;
    }
  }
  if (j >= rightArrayLenght)
  {
    while (i < leftArrayLenght)
    {
      mergedArray[k] = leftArray[i];
      i++;
      k++;
    }
  }
}```
示例分析
这个程序示例很简单,演示了线程的基本用法。该示例背后的思想是,为了节省执行时间而添加并行,把问题划分为几个小问题,并分配给几个线程(分而治之)。我们在前面提到过,把问题划分成若干更小的单元,更容易在实现中创建并行逻辑。同时,在并行中使用系统资源能优化应用程序并提高其运行速度。

更多讨论
如前所述,每个应用程序都有一个主线程。使用`CreateThreadWin32 API`创建其他线程:

HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes,
  SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress,
  LPVOID lpParameter, DWORD dwFlags, LPDWORD lpThreadId );`
设置线程的开始地址(lpStartAddress)和设置传给线程例程的值(lpParameter)很重要。lpParameter是一个预定义例程(函数)指针,如下代码所示:

typedef DWORD ( WINAPI *PTHREAD_START_ROUTINE )( LPVOID lpThreadParameter );`
我们的`ThreadStart`方法与指定的原型匹配,这也是开始执行线程的地方。`CreateThreadAPI`的第4个参数是一个要传递给线程例程的指针。如果要传递更多参数,可以创建一个结构或类,然后传递相应对象的地址。欲详细了解`CreateThreadAPI`,请查阅MSDN(http://msdn.microsoft.com/en-us/library/windows/desktop/ms682453%28v=vs.85%29.aspx)。
相关文章
|
1月前
|
缓存 NoSQL 中间件
Redis的线程模型
Redis采用单线程模型确保操作的原子性,每次只执行一个操作,避免并发冲突。它通过MULTI/EXEC事务机制、Lua脚本和复合指令(如MSET、GETSET等)保证多个操作要么全成功,要么全失败,确保数据一致性。Redis事务在EXEC前失败则不执行任何操作,EXEC后失败不影响其他操作。Pipeline虽高效但不具备原子性,适合非热点时段的数据调整。Redis 7引入Function功能,支持函数复用,简化复杂业务逻辑。总结来说,Redis的单线程模型简单高效,适用于高并发场景,但仍需合理选择指令执行方式以发挥其性能优势。
60 6
|
2月前
|
存储 缓存 关系型数据库
MySQL底层概述—3.InnoDB线程模型
InnoDB存储引擎采用多线程模型,包含多个后台线程以处理不同任务。主要线程包括:IO Thread负责读写数据页和日志;Purge Thread回收已提交事务的undo日志;Page Cleaner Thread刷新脏页并清理redo日志;Master Thread调度其他线程,定时刷新脏页、回收undo日志、写入redo日志和合并写缓冲。各线程协同工作,确保数据一致性和高效性能。
MySQL底层概述—3.InnoDB线程模型
|
5月前
|
自然语言处理 编译器 Linux
告别头文件,编译效率提升 42%!C++ Modules 实战解析 | 干货推荐
本文中,阿里云智能集团开发工程师李泽政以 Alinux 为操作环境,讲解模块相比传统头文件有哪些优势,并通过若干个例子,学习如何组织一个 C++ 模块工程并使用模块封装第三方库或是改造现有的项目。
553 56
|
6月前
|
安全 程序员 编译器
【实战经验】17个C++编程常见错误及其解决方案
想必不少程序员都有类似的经历:辛苦敲完项目代码,内心满是对作品品质的自信,然而当静态扫描工具登场时,却揭示出诸多隐藏的警告问题。为了让自己的编程之路更加顺畅,也为了持续精进技艺,我想借此机会汇总分享那些常被我们无意间忽视却又导致警告的编程小细节,以此作为对未来的自我警示和提升。
941 16
|
4月前
|
机器学习/深度学习 人工智能 自然语言处理
C++构建 GAN 模型:生成器与判别器平衡训练的关键秘籍
生成对抗网络(GAN)是AI领域的明星,尤其在C++中构建时,平衡生成器与判别器的训练尤为关键。本文探讨了GAN的基本架构、训练原理及平衡训练的重要性,提出了包括合理初始化、精心设计损失函数、动态调整学习率、引入正则化技术和监测训练过程在内的五大策略,旨在确保GAN模型在C++环境下的高效、稳定训练,以生成高质量的结果,推动AI技术的发展。
150 10
|
6月前
|
并行计算 JavaScript 前端开发
单线程模型
【10月更文挑战第15天】
|
5月前
|
缓存 安全 C++
C++无锁队列:解锁多线程编程新境界
【10月更文挑战第27天】
329 7
|
5月前
|
消息中间件 存储 安全
|
6月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
96 1
|
6月前
|
存储 并行计算 安全
C++多线程应用
【10月更文挑战第29天】C++ 中的多线程应用广泛,常见场景包括并行计算、网络编程中的并发服务器和图形用户界面(GUI)应用。通过多线程可以显著提升计算速度和响应能力。示例代码展示了如何使用 `pthread` 库创建和管理线程。注意事项包括数据同步与互斥、线程间通信和线程安全的类设计,以确保程序的正确性和稳定性。
115 5

热门文章

最新文章