关于多线程

概念

进程:正在运行的程序,不仅指处于执行期的程序本身,还包括它所管理的资源,比如由它打开的窗口,地址的资源,进程状态等等

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

进程好比工厂的车间,它代表CPU所能处理的单个任务,工厂给车间资源、线程,空间

线程好比车间里的工人

为什么使用多线程

1.如果只有一个车间,但是要完成多个任务,只要第一个任务完不成,其他任务都会被阻塞,所以要同时开展多个车间完成多个任务

2.CPU速度>内存速度>磁盘IO速度,当CPU执行完后,可能内存、磁盘还没有执行完,CPU这时处于空转状态,如果它没有去处理新的请求,就会导致程序性能很差劲

3.一个进程有4G的虚拟内存,多线程可以共享这个内存空间

函数详解

createthread

HANDLE CreateThread  (
    LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
    SIZE_T dwStackSize,
    LPTHREAD_START_ROUTINE lpStartAddress,
    LPVOID lpParameter,
    DWORD dwCreationFlags,
    LPDWORD lpThreadId    )

第一个参数 lpThreadAttributes 表示线程内核对象的安全属性,一般传入
NULL 表示使用默认设置。

知识扩展*:attribute基本都表示内核对象

第二个参数 dwStackSize 表示线程栈空间大小。传入 0 表示使用默认大小(1MB)。
第三个参数 lpStartAddress 表示新线程所执行的线程函数地址,多个线程
可以使用同一个函数地址。

第四个参数 lpParameter 是传给线程函数的参数。
第五个参数 dwCreationFlags 指定额外的标志来控制线程的创建,为 0 表示线程创建之后立即就可以进行调度,如果为 CREATE_SUSPENDED 则表示线程创建后暂停运行,这样它就无法调度,直到调用 ResumeThread()。
第六个参数 lpThreadId 将返回线程的 ID 号,传入 NULL 表示不需要返回该线程 ID 号

beginthreadex

基本参数和createthread差不多,他是createthread的封装后的版本

代码实战

调用多线程函数要包含头文件  #include <process.h>

    int arg1 = 100; int arg2 = 200; int arg3 = 300;
	unsigned int one, two, three;
	/包含头文件proces.h
	/查看定义,第三个参数为指针类型
	/第四个参数为函数参数,类型必须为void*
	_beginthreadex(NULL, 0, &work1, (void*)&arg1, 0, &one);
	_beginthreadex(NULL, 0, &work2, (void*)&arg2, 0, &two);

第三个参数必须为函数名的指针,而且这个函数的返回值必须是unsigned  WINAPI ,WINAPI 也可以写成__stdcall,最后一个参数类型必须为unsigned*

unsigned WINAPI work1(void* arg1)
{
	int count = *((int*)arg1);
	for (int i=0;i<count;i++)
	{
		std::cout << "1 is work"<<std::endl;
		Sleep(1000);
	}
	return 0;
}
unsigned  WINAPI work2(void* arg2)
{
	int count = *((int*)arg2);
	for (int i = 0; i < count; i++)
	{
		std::cout << "2 is work" << std::endl;
		Sleep(2000);
	}
	return 0;
}

多线程执行时,线程执行顺序不分先后,随机

子线程的麻烦

当main结束后,子线程也会被终止,要么我们加上system pause,要么我们使用延时函数,不让main结束,那还有其他办法吗?这里遇到的情况就要分为两种

1.子线程为单线程   2。子线程为多线程

我们先介绍一下内核对象

内核对象

内核对象就是我们创建的一块内存,而且只能由操作系统内核访问,它包括线程、进程、文件等,它就是为了方便管理线程、进程等资源而由操作系统创建的一个数据块,其创建的所有者肯定时操作系统

调用创建内核对象的函数后,会返回一个句柄,句柄标识了所创建的对象

1.子线程为单线程

解决方法waitForSingleObject 

waitForSingleObject (
_In_ HANDLE hHandle,         /指明一个内核对象的句柄
_In_ DWORD dwMilliseconds    /等待时间
);

作用:等到内核对象变为已通知状态,只有内核对象,即本例中的多线程结束后,才处于通知状态

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

这里一开始会打印begin,只有hThread线程结束后,才会打印end,即waitforsingleobject函数会阻塞在这里

2.子线程为多线程

如果子线程有多个线程,那该怎么处理呢?我们可以用WaitForMultipleObjects函数

函数原型

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

第一个参数为多线程的句柄个数,第二个表示句柄数组,第三个true表示所有线程都发出信号,才不阻塞,第四个参数I为NFINITE时,表示直到线程结束时间才会结束

,代码实战

#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;
}

这里表示有50个线程,第0个线程为num-=1,第1个线程为num+=1,最后的结果是什么呢“?不是0,如果每个线程轮流加减,最后的结果为0,但是结果却是一些很大的数,要么是很大的负数,要么是很大的正数,为什么呢

因为计算是通过CPU来计算的,比如计算第0 个线程时,CPU从内存中取得num的值,计算后暂时放入CPU中,此时num的值为x,而CPU中的num值为x-500000,因为线程是随机的,这时有可能会执行线程1,这时内存中值为x,执行线程1后,值为x+500000,然后又执行线程2,因为之前CPU里已经有值了,所以这时CPU的值又为x-500000,,但是如果按照正常逻辑,第0次,x-50000,第1次x-500000+500000,当执行第线程2时,x为x-5,虽然结果还是一样,但是原理却不同,而且,线程是随机的,就会导致结果有很大的出入。

用图表示

 这是内存中实际的线程切换,图1表示从内存拿到数据后,没来得及返回值,就直接切换到了线程B,这样也算是执行了线程A一次

 这时按照我们正常逻辑推断的图,可以看到索然执行结果虽然一样,但是原理却不一样

互斥对象

为了避免上述,线程没来得及就转移的情况,我们可以使用互斥对象

互斥对象属于内核对象,它能够确保线程对单个资源的互斥访问权

互斥对象包含一个使用数量,一个线程 ID 和一个计数器。其中线程 ID 用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

HANDLE  WINAPI    CreateMutexW (
    _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes, /指向安全属性
    _In_ BOOL bInitialOwner, /初始化互斥对象的所有者 TRUE 立即拥有互斥体
    _In_opt_ LPCWSTR lpName  /指向互斥对象名的指针,可以自己取名
)

首先创建一个互斥对象, 

hMutex=CreateMutex(NULL, FALSE, NULL);

TRUE:立即占有该互斥量

FALSE,互斥量处于触发/有信号/已通知状态,不为任何线程所占用

 然后在线程A和线程B都里加上互斥锁

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;
}

如果运行线程A,等待互斥量B ReleaseMutex,意思是直到线程B完全执行后,才能执行线程A

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/22294.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

[组合数学] 排列组合

文章目录 加法法则 --每一类都能够独立的完成任务乘法法则 --集合论 任务分步骤1000和9999之间有多个具有不同数位的奇数n 7 3 1 1 2 1 3 4 ^311^213^4 3112134 求除尽n的整数个数0到10000之间有多少个整数恰好是有一位数字是5两位数字可以组成多少两位互异且非零的两位数 减…

Flutter音乐播放audioplayers

简介 Flutter的audioplayers是一个Flutter插件&#xff0c;可以播放多个同时的音频文件&#xff0c;支持Android、iOS、Linux、macOS、Windows和web平台。它有以下特点&#xff1a; 可以从本地文件、网络资源或内存中加载音频可以控制音量、进度、速度和循环可以播放多个音频…

pg事务:隔离级别历史与SSI

事务隔离级别的历史 ANSI SQL-92定义的隔离级别和异常现象确实对数据库行业影响深远&#xff0c;甚至30年后的今天&#xff0c;绝大部分工程师对事务隔离级别的概念还停留在此&#xff0c;甚至很多真实的数据库隔离级别实现也停留在此。但后ANSI92时代对事物隔离有许多讨论甚至…

Everypixel: AI图片搜索引擎

【产品介绍】 Everypixel是一个基于人工智能的图片搜索引擎。可以搜索超过 50 个图片来源的优质的授权图库版权素材图片&#xff0c;还可以使用免费图案功能&#xff0c;找到适合自己需求的可定制无缝图案。 Everypixel利用深度学习和计算机视觉技术&#xff0c;为客户提供先进…

黑客入门指南,学习黑客必须掌握的技术

黑客一词&#xff0c;原指热心于计算机技术&#xff0c;水平高超的电脑专家&#xff0c;尤其是程序设计人员。是一个喜欢用智力通过创造性方法来挑战脑力极限的人&#xff0c;特别是他们所感兴趣的领域&#xff0c;例如电脑编程等等。 提起黑客&#xff0c;总是那么神秘莫测。…

VONR排查指导分享

不能注册或呼叫到SIP服务器端30秒挂断呼叫的黄金法则咬线或摘机状态单通或无语音收到400 bad request收到413&#xff0c;513 Request Entity Too Large或Message Too Large消息收到408&#xff0c; 480或者487 消息483 - Too Many Hops488 – Not Acceptable Here语音质量和思…

iptables 防火墙(一)

目录 一&#xff1a;iptables概述 二&#xff1a;netfilter/iptables关系 三&#xff1a;四表五链 1.规则表和规则链的作用 2. 四表 3.五链 ​4.规则链之间的匹配顺序 &#xff08;1&#xff09;主机型防火墙 &#xff08;2&#xff09;网络型防火墙 5.规则链内的匹配…

【JVM】5. 本地方法接口和本地方法栈

文章目录 5.1. 什么是本地方法&#xff1f;5.2. 为什么使用Native Method&#xff1f;5.3. 本地方法栈 5.1. 什么是本地方法&#xff1f; 简单地讲&#xff0c;一个Native Method是一个Java调用非Java代码的接囗。一个Native Method是这样一个Java方法&#xff1a;该方法的实现…

UE Http Server 插件说明

1. Create Http Server 创建Http服务器。 Port : 监听端口&#xff0c;范围 1 - 65535&#xff0c;要保证系统唯一&#xff0c;不然会监听失败。 2. Bind 绑定网页路由回调。 Target &#xff1a;HttpServer 对象 Http Path: 绑定路径&#xff0c;如 ”/index“ Http Verbs…

与vCenter无法通讯时更改虚拟机的网络配置

客户的VCSA由于虚拟机的配置问题导致无法启动&#xff0c;需要通过重新创建VCSA的虚拟机配置的方式来恢复。但是&#xff0c;由于ESXi主机上的所有物理网口都已分配给了分布式网络交换机&#xff0c;在重建虚拟机配置时不能指定标准交换机的端口组来配置网络。而如果将虚拟机的…

【C++进阶之路】模板

前言 假如需要你写一个交换函数&#xff0c;交换两个相同类型的值&#xff0c;这时如果交换的是int 类型的值&#xff0c;你可能会写一个Swap函数&#xff0c;其中参数是两个int类型的&#xff0c;假如再让你写一个double类型的呢&#xff1f;你可能又要写一个Swap的函数重载&…

GD32F4x 加密(开启读保护功能)

参考链接1&#xff1a;&#xff08;设置读保护&#xff09; GD32F4x 如何开启读保护功能&#xff08;芯片加密&#xff09;&#xff1f;_EmbeddedOsprey的博客-CSDN博客 参考链接2&#xff1a;读取芯片ID进行加密 《嵌入式 – GD32开发实战指南》第19章 程序加密_gd32大小端…

训练/测试、过拟合问题

在机器学习中&#xff0c;我们创建模型来预测某些事件的结果&#xff0c;比如之前使用重量和发动机排量&#xff0c;预测了汽车的二氧化碳排放量 要衡量模型是否足够好&#xff0c;我们可以使用一种称为训练/测试的方法 训练/测试是一种测量模型准确性的方法 之所以称为训练…

黑客最常用的10款黑客工具

以下所有这些工具都是捆绑在一起的Linux发行版&#xff0c;如Kali Linux或BackBox&#xff0c;所以我们一定会建议您安装一个合适的Linux黑客系统&#xff0c;使您的生活更轻松 - 尤其是因为这些黑客工具可以&#xff08;自动&#xff09;更新。 1、Nikto&#xff08;网站漏洞…

lwIP更新记01:全局互斥锁替代消息机制

从 lwIP-2.0.0 开始&#xff0c;在 opt.h 中多了一个宏开关 LWIP_TCPIP_CORE_LOCKING&#xff0c;默认使能。这个宏用于启用 内核锁定 功能&#xff0c;使用 全局互斥锁 实现。在之前&#xff0c;lwIP 使用 消息机制 解决 lwIP 内核线程安全问题。消息机制易于实现&#xff0c;…

winpcap 发包工具

本工具主要用来进行网络协议的调试&#xff0c;主要方法是&#xff0c;对现场数据抓包&#xff0c;然后将数据包带回交给开发人员&#xff0c;开发人员将该数据包重新发送和处理&#xff0c;模拟现场环境以便于调试和分析。 &#xff08;一&#xff09;使用方法 命令行下输入s…

Visual Studio插件DevExpress CodeRush v22.1- 支持C# 10

DevExpress CodeRush是一个强大的Visual Studio .NET 插件&#xff0c;它利用整合技术&#xff0c;通过促进开发者和团队效率来提升开发者体验。为Visual Studio IDE增压、消除重复的代码并提高代码质量&#xff0c;可以快速思考、自动化测试、可视化调试和重构。 CodeRush v2…

有了 IP 地址,为什么还要用 MAC 地址?

MAC地址等价于快递包裹上的收件人姓名。 MAC地址更多是用于确认对方信息而存在的。就如同快递跨越几个城市来到你面前&#xff0c;快递员需要和你确认一下收件人是否正确&#xff0c;才会把包裹交给你一样。 IP66在线查IP地址位置&#xff1a;https://www.ip66.net/?utm-sour…

软件设计师 数据库刷题项并包含知识点总结

**两级映像 有概念模式和内模式跟物理独立性相关&#xff0c;有外模式和概念模式跟逻辑独立性相关 ** 属性列就是RS共同拥有的ABC&#xff0c;一般去除后面的&#xff0c;所以就只有前面三个ABC&#xff0c;元组就是有没有自然连接成功的&#xff0c;就是R.AS.A R.BS.B… 选项里…

希望所有计算机专业同学都知道这些老师

C语言教程——翁凯老师、赫斌 翁恺老师是土生土长的浙大码农&#xff0c;从本科到博士都毕业于浙大计算机系&#xff0c;后来留校教书&#xff0c;一教就是20多年。 翁恺老师的c语言课程非常好&#xff0c;讲解特别有趣&#xff0c;很适合初学者学习。 郝斌老师的思路是以初学…