首页 > 其他分享 >MFC---多线程(基本概念和线程同步之互斥对象)

MFC---多线程(基本概念和线程同步之互斥对象)

时间:2024-01-16 16:32:14浏览次数:26  
标签:include int unsigned --- 互斥 对象 线程 多线程


基本概念

引入一个题目:
Bingo老师 提了一个需求 :打印
每隔3秒叫martin老师做一次俯卧撑 持续20次
每隔1秒钟叫rock老师甩头发 持续50次
每隔2秒钟叫西西老师唱歌 持续40次

线程(CPU调度和分派的基本单位)

线程是在进程中产生的一个执行单元,是CPU调度和分配的最小单元,其在同一个进程中与其他线程并行运行,他们可以共享进程内的资源,比如内存、地址空间、打开的文件等等。

进程(分配资源的基本单位)

进程:正在运行的程序是处于执行期的程序以及它所管理的资源(如打开的文件、挂起的信号、进程状态、地址空间等等)的总称,从操作系统核心角度来说,进程是操作系统调度除CPU时间片外进行的资源分配和保护的基本单位,它有一个独立的虚拟地址空间,用来容纳进程映像(如与进程关联的程序与数据),并以进程为单位对各种资源实施保护,如受保护地访问处理器、文件、外部设备及其他进程(进程间通信)

MFC---多线程(基本概念和线程同步之互斥对象)_c++

计算机有很多资源组成,比如CPU、内存、磁盘、鼠标、键盘等,就像一个工厂由电力系统、作业车间、仓库、管理办公室和工人组成

MFC---多线程(基本概念和线程同步之互斥对象)_mfc_02

假定工厂的电力有限,一次只能供给一个或少量几个车间使用。也就是说,一部分车间开工的时候,其他车间都必须停工。背后的含义就是,单个CPU一次只能运行一个任务,多个CPU能够运行少量任务。

MFC---多线程(基本概念和线程同步之互斥对象)_互斥对象_03


MFC---多线程(基本概念和线程同步之互斥对象)_c++_04

线程就好比车间里的工人。一个进程可以包括多个线程,他们协同完成某一个任务。

MFC---多线程(基本概念和线程同步之互斥对象)_内核对象_05

为什么使用多线程

避免阻塞

大家知道,单个进程只有一个主线程,当主线程阻塞的时候,整个进程也就阻塞了,无法再去做其它的一些功能了。

避免CPU空转

应用程序经常会涉及到RPC,数据库访问,磁盘IO等操作,这些操作的速度比CPU慢很多,而在等待这些响应时,CPU却不能去处理新的请求,导致这种单线程的应用程序性能很差。

提升效率

一个进程要独立拥有4GB的虚拟地址空间,而多个线程可以共享同一地址空间,线程的切换比进程的切换要快得多。

上下文切换

MFC---多线程(基本概念和线程同步之互斥对象)_c++_06

线程创建函数

CreateThread

CreateThread是一种微软在Windows API中提供了建立新的线程的函数,该函数在主线程的基础上创建一个新线程。线程终止运行后,线程对象仍然在系统中,必须通过CloseHandle函数来关闭该线程对象。

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
SIZE_T dwStackSize,//initialstacksize
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
LPVOID lpParameter,//threadargument
DWORD dwCreationFlags,//creationoption
LPDWORD lpThreadId//threadidentifier
)

第一个参数 lpThreadAttributes 表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。
第二个参数 dwStackSize 表示线程栈空间大小。传入0表示使用默认大小(1MB)。
第三个参数 lpStartAddress 表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。
第四个参数 lpParameter 是传给线程函数的参数。
第五个参数 dwCreationFlags 指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为CREATE_SUSPENDED则表示线程创建后暂停运行,这样它就无法调度,直到调用ResumeThread()。
第六个参数 lpThreadId 将返回线程的ID号,传入NULL表示不需要返回该线程ID号

_beginthreadex

unsigned long _beginthreadex(
  
    void *security,    // 安全属性, 为NULL时表示默认安全性
 
    unsigned stack_size,    // 线程的堆栈大小, 一般默认为0
 
    unsigned(_stdcall *start_address)(void *),   // 线程函数
 
    void *argilist, // 线程函数的参数
 
   unsigned initflag,    // 新线程的初始状态,0表示立即执行,//CREATE_SUSPENDED表示创建之后挂起
    unsigned *threaddr    // 用来接收线程ID
 
);

返回值 : // 成功返回新线程句柄, 失败返回0
__stdcall表示
1.参数从右向左压入堆栈
2.函数被调用者修改堆栈

#include <stdio.h>
#include <windows.h>
#include <process.h>

DWORD WINAPI ThreadFun(LPVOID p)
{
	int iMym = *((int*)p);
	printf("我是子线程,PID = %d,iMym = %d\n", GetCurrentThreadId(), iMym);
	return 0;
}

int main()
{
	printf("main begin\n");

	HANDLE hThread;
	DWORD dwThreadID;
	int m = 100;
	hThread = CreateThread(NULL, 0, ThreadFun, &m, 0, &dwThreadID);
	printf("我是主线程,PID = %d\n", GetCurrentThreadId());
	CloseHandle(hThread);
	Sleep(2000);
	system("pause");
	return 0;
}

简单多线程示例

理解内核对象

定义:
内核对象通过API来创建,每个内核对象是一个数据结构,它对应一块内存,由操作系统内核分配,并且只能由操作系统内核访问。在此数据结构中少数成员如安全描述符和使用计数是所有对象都有的,但其他大多数成员都是不同类型的对象特有的。内核对象的数据结构只能由操作系统提供的API访问,应用程序在内存中不能访问。调用创建内核对象的函数后,该函数会返回一个句柄,它标识了所创建的对象。它可以由进程的任何线程使用。
CreateProcess
CreateThread
CreateFile
event
Job
Mutex
常见的内核对象 : 进程、线程、文件,存取符号对象、事件对象、文件对象、作业对象、互斥对象、管道对象、等待计时器对象,邮件槽对象,信号对象
内核对象:为了管理线程/文件等资源而由操作系统创建的数据块。
其创建的所有者肯定是操作系统。

第一阶段:主线程和子线程的结束时间

main函数返回后,整个进程终止,同时终止其包含的所有线程。。。

#include <stdio.h>
#include <windows.h>
#include <process.h>   

unsigned WINAPI ThreadFunc(void *arg);

int main(int argc, char *argv[]) 
{
	HANDLE hThread;
	unsigned threadID;
	int param=5;

	hThread=(HANDLE)_beginthreadex(NULL, 0, ThreadFunc, (void*)¶m, 0, &threadID);
	if(hThread==0)
	{
		puts("_beginthreadex() error");
		return -1;
	}
	Sleep(3000);
	puts("end of main");
	return 0;
}


unsigned WINAPI ThreadFunc(void *arg)
{
	int i;
	int cnt=*((int*)arg);
	for(i=0; i<cnt; i++)
	{
		Sleep(1000);  puts("running thread");	 
	}
	return 0;
}

第二阶段(WaitForSingleObject)

来等待一个内核对象变为已通知状态

WaitForSingleObject(
    _In_ HANDLE hHandle,    //指明一个内核对象的句柄
    _In_ DWORD dwMilliseconds     //等待时间
);
#include <stdio.h>
#include <windows.h>
#include <process.h>


unsigned int __stdcall ThreadFun(LPVOID p)
{
	int cnt = *((int*)p);
	for (int i = 0; i < cnt; i++)
	{
		Sleep(1000);
		puts("running thread");
	}
	return 0;
}


int main()
{
	printf("main begin\n");
	int iParam = 5;
	unsigned int dwThreadID;
	DWORD wr;

	HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun,
		(void*)&iParam, 0, &dwThreadID);

	if (hThread == NULL)
	{
		puts("_beginthreadex() error");
		return -1;
	}
	// 
	printf("WaitForSingleObject begin\n");
	if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED)
	{
		puts("thread wait error");
		return -1;
	}
	printf("WaitForSingleObject end\n");

	printf("main end\n");
	system("pause");
	return 0;
}

第三阶段(WaitForMultipleObjects)

起两个线程,一个加+1,一个减1

WaitForMultipleObjects(
    _In_ DWORD nCount,    // 要监测的句柄的组的句柄的个数
    _In_reads_(nCount) CONST HANDLE* lpHandles,   //要监测的句柄的组
    _In_ BOOL bWaitAll,  // TRUE 等待所有的内核对象发出信号, FALSE 任意一个内核对象发出信号
    _In_ DWORD dwMilliseconds //等待时间
    );
#include <stdio.h>
#include <windows.h>
#include <process.h>

#define NUM_THREAD	50
unsigned WINAPI threadInc(void * arg);
unsigned WINAPI threadDes(void * arg);
long long num=0;

int main(int argc, char *argv[]) 
{
	HANDLE tHandles[NUM_THREAD];
	int i;

	printf("sizeof long long: %d \n", sizeof(long long));
	for(i=0; i<NUM_THREAD; i++)
	{
		if(i%2)
		    tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
		else
		    tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
	}

	WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
	printf("result: %lld \n", num);
	return 0;
}

unsigned WINAPI threadInc(void * arg) 
{
	int i;
	for(i=0; i<500000; i++)
		num+=1;
	return 0;
}
unsigned WINAPI threadDes(void * arg)
{
	int i;
	for(i=0; i<500000; i++)
		num-=1;
	return 0;
}

线程同步—互斥对象

互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。
互斥对象包含一个使用数量,一个线程ID和一个计数器。其中线程ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。
创建互斥对象:调用函数CreateMutex。调用成功,该函数返回所创建的互斥对象的句柄。
请求互斥对象所有权:调用函数WaitForSingleObject函数。线程必须主动请求共享对象的所有权才能获得所有权。
释放指定互斥对象的所有权:调用ReleaseMutex函数。线程访问共享资源结束后,线程要主动释放对互斥对象的所有权,使该对象处于已通知状态。

CreateMutex

HANDLE
WINAPI
CreateMutexW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,   //指向安全属性
    _In_ BOOL bInitialOwner,   //初始化互斥对象的所有者  TRUE 立即拥有互斥体
    _In_opt_ LPCWSTR lpName    //指向互斥对象名的指针  L“Bingo”
    );
#include <stdio.h>
#include <windows.h>
#include <process.h>

#define NUM_THREAD	50
unsigned WINAPI threadInc(void * arg);
unsigned WINAPI threadDes(void * arg);

long long num=0;
HANDLE hMutex;

int main(int argc, char *argv[]) 
{
	HANDLE tHandles[NUM_THREAD];
	int i;

	hMutex=CreateMutex(NULL, FALSE, NULL);
	for(i=0; i<NUM_THREAD; i++)
	{
		if(i%2)
		    tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
		else
		    tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
	}

	WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
	CloseHandle(hMutex);
	printf("result: %lld \n", num);
	return 0;
}

unsigned WINAPI threadInc(void * arg) 
{
	int i;

	WaitForSingleObject(hMutex, INFINITE);
	for(i=0; i<500000; i++)
		num+=1;
	ReleaseMutex(hMutex);

	return 0;
}
unsigned WINAPI threadDes(void * arg)
{
	int i;

	WaitForSingleObject(hMutex, INFINITE);
	for(i=0; i<500000; i++)
		num-=1;
	ReleaseMutex(hMutex);

	return 0;
}


标签:include,int,unsigned,---,互斥,对象,线程,多线程
From: https://blog.51cto.com/u_15305087/9273790

相关文章

  • C++---打开txt文件进行字符串的查找替换最终生成Excel文件
    #include<iostream>#include<fstream>#include<sstream>#include<string>#include<vector>#include<algorithm>#include<iomanip>usingnamespacestd;voidfindAndReplaceAll(string&source,conststring&......
  • MFC---多线程(线程同步之关键代码段)
    关键代码段,也称为临界区,工作在用户方式下。它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。通常把多线程中访问同一种资源的那部分代码当做关键代码段。1.初始化关键代码段调用InitializeCriticalSection函数初始化一个关键代码段。InitializeCriticalSection(......
  • 方法论:仓储物流规划--数据分析(转)
     老K-LaoK专栏同名微信公众号:智能仓储物流技术研习社。​关注他 8人赞同了该文章导语大家好,我是智能仓储物流技术研习社的社长,你的老朋友,老K。知识星球 * 原创电子书 * 深海社区 * 微信群文:尹军琪在做物流规划设计时,人们往往对设计指标......
  • Unity FaceBook SDK - 1
    SDK下载前往https://developers.facebook.com/docs/unity/downloads下载SDKps目前我只用过16.0.0跑通 在facebook上搞出应用后,将应用的app相对于数据复制过来如图下: 安卓的话将上面的三个内容复制到facebook,然后点击一下按钮,生成出manifest 然后可以参考使......
  • svg使用封装-vue
    我们在项目中经常会使用到svg,这里对svg进行封装,以方便后续的使用。1.安装svg插件npmivite-plugin-svg-icons2.在vite.config.ts中引入,用来指定svg存放位置import{createSvgIconsPlugin}from"vite-plugin-svg-icons";import{resolve}from"path";constplugin......
  • AD采集卡设计方案:630-基于PCIe的高速模拟AD采集卡
     基于PCIe的高速模拟AD采集卡一、产品概述   基于PCIe的一款分布式高速数据采集系统,实现多路AD的数据采集,并通过PCIe传输到存储计算服务器,实现信号的分析、存储。    产品固化FPGA逻辑,适配2路1Gsps/2路2Gsps采集,实现PCIe的触发采集,单次采集容量2G......
  • (vcpu-0)Exception 0xc0000005 (access violation) has occurred
    VM安装window10报错如何解决?(vcpu-0)Exception0xc0000005(accessviolation)hasoccurred.(vcpu-1)Exception0xc0000005(accessviolation)hasoccurred.除了常规的虚拟化的一些原因外,如果网上的方式你都试过了,不能解决的话;那么请升级你的VMware到16.2.*版本即可;......
  • docker - 将几个目录复制到另一个目录
    您如何将多个目录复制到Docker中的目标目录?我不想复制目录内容,而是复制整个目录结构。COPY和ADD命令复制目录内容,展平结构,这是我不想要的。也就是说,如果这些是我的来源:.├──a│  ├──aaa.txt│  └──uuu.txt├──b│  ├──ooo.txt│  └──p......
  • [cpp]: operator""s -- <string>
    [cpp]: operator""s -- <string>    1operator""s:将一个字符数组字面量转化为【basic_string】类型数据。1.1#include<string>1.2operator""s :convertsacharacterarrayliteralto basic_string   2e.g.......
  • 无涯教程-SQL - Transactions(事务)
    事务是将一个或多个更改打包在一起保存到数据库,事务对于确保数据完整性和处理数据库错误很重要。事务性质事务具有以下四个标准属性,通常以首字母缩写ACID表示。原子性-确保工作单元内的所有操作均成功完成,否则,事务将在失败时中止,并且所有先前的操作都将还原到以前的状态......