Windows中多线程的基础知识1——互斥对象

news2024/11/25 13:22:54

目录

  • 1 多线程的基本概念
    • 1.1 进程
      • 一、程序和进程的概念
      • 二、进程组成
      • 三、进程地址空间
    • 1.2 线程
      • 一、线程组成
      • 二、线程运行
      • 三、线程创建函数
    • 1.3 多进程与多线程并发
      • 一、多进程并发
      • 二、多线程并发
  • 2 线程同步
    • 2.1 一个经典的线程同步问题
    • 2.2 利用互斥对象实现线程同步
      • 一、创建互斥对象
      • 二、获得互斥对象所有权
      • 三、释放互斥对象的所有权
      • 四、如何利用互斥对象实现线程同步
    • 2.3 利用互斥对象的代码实例

1 多线程的基本概念

1.1 进程

一、程序和进程的概念

 程序是计算机指令的集合,它以文件的形式存储在磁盘上。而进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。进程是资源申请、调度和独立运行的单位。

二、进程组成

 进程由两部分组成:
 1.操作系统用来管理进程的内核对象
 内核对象是系统用来存放关于进程的统计信息的地方。内核对象是操作系统内部分配的一个内存块,该内存块是一种数据结构,其成员负责维护对象的各种信息。由于内核对象的数据结构只能被内核访问使用,因此应用程序在内存中无法找到该数据结构,并直接改变其内容,只能通过Windows提供的一些函数来对内核对象进行操作。
 2.地址空间
 它包含所有可执行模块或DLL模块的代码和数据。另外,它也包含动态内存分配的空间,例如线程的栈(stacks)和堆(heap)分配空间。进程从来不执行任何东西,它只是线程的容器。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码。也就说,真正完成代码执行的是线程,而进程只是线程的容器,或者说线程的执行环境。
 单个进程可以包含多个线程,这些线程都“同时”执行进程地址空间中的代码,每个进程至少拥有一个线程。当创建一个进程时,操作系统会自动创建这个进程的第一个线程,成为主线程,也就是执行main或者WinMain函数的线程。此后,主线程可以创建其他线程。

三、进程地址空间

 系统赋予每个进程独立的虚拟地址空间。对于32位进程来说,这个空间地址是4GB。因为对32位指针来说,它能寻址的范围是2^32,即4GB。每个进程都有它自己的私有地址空间。

1.2 线程

一、线程组成

 线程由两部分组成:
 (1)线程的内核对象。操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。
 (2)线程栈(stack)。它用于维护线程在执行代码时需要的所有函数参数和局部变量。
 当创建线程时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。
 线程总是在某个进程环境中创建。线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易的通信。

二、线程运行

 操作系统为每一个运行线程安排一定的CPU时间——时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间片内运行,因时间片相当短,因此给用户的感觉就好像多个线程是同时运行一样。如果计算机拥有多个CPU,线程就能真正意义上同时运行了。

三、线程创建函数

创建线程可以使用系统提供的API函数:CreateThread来完成,该函数的原型声明如下所述:

HANDLE WINAPI CreateThread(
__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
__in SIZE_T dwStackSize,
__in LPTHREAD_START_ROUTINE lpStartAddress,
__in_opt LPVOID lpParameter,
__in DWORD dwCreationFlags,
__out_opt LPDWORD lpThreadId
);

  1. 返回值:返回线程句柄
    “句柄” 类似指针, 但通过指针可读写对象, 通过句柄只是使用对象;有句柄的对象一般都是系统级别的对象(或叫内核对象);之所以给我们的是句柄而不是指针,目的只有一个:“安全”;似乎通过句柄能做很多事情, 但一般把句柄提交到某个函数(一般是系统函数)后,我们也就到此为止很难了解更多了。
    不管是指针还是句柄, 都不过是内存中的一小块数据(一般用结构描述), 微软并没有公开句柄的结构细节, 猜一下它应该包括: 真实的指针地址、访问权限设置、引用计数等等.
    既然 CreateThread 可以返回一个句柄,说明线程属于 “内核对象”。实际上不管线程属于哪个进程, 它们在系统中是平等的;在优先级相同的情况下, 系统会在相同的时间间隔内来运行一下每个线程, 不过这个间隔很小很小, 以至于让我们误以为程序是在不间断地运行。这时你应该有一个疑问: 系统在去执行其他线程的时候, 是怎么记住前一个线程的数据状态的?有这样一个结构 TContext, 它基本上是一个 CPU 寄存器的集合,线程是数据就是通过这个结构切换的。
  2. 参数2:堆栈大小
    CreateThread 的第二个参数是分配给线程的堆栈大小。每个线程都有自己独立的堆栈(也拥有自己的消息队列)。什么是堆栈? 其实堆是堆、栈是栈, 有时 “栈” 也被叫做 “堆栈”。它们都是进程中的内存区域, 主要是存取方式不同(栈:先进后出; 堆:先进先出)。
    现在我们知道了线程有自己的 “栈”, 并且在建立线程时可以分配栈的大小。如果这个值都是 0, 这表示使用系统默认的大小, 默认和主线程栈的大小一样, 如果不够用会自动增长;那主线程的栈有多大? 这个值是可以设定的: Project -> Options -> Linking
    在这里插入图片描述
  3. 参数3:指向函数的指针
    指向应用程序定义的LPTHREAD_START_ROUTINE类型的函数的指针,这个函数将由新线程执行,表明新线程的起始地址。线程入口函数的原型是:
    DWORD WINAPI ThreadProc( __in LPVOID lpParameter);
    请注意,线程入口函数是有返回值的,通过GetExitCodeThread 可以获取指定线程的终止状态码。
  4. 参数4:
    线程入口函数的参数是个无类型指针(Pointer), 用它可以指定任何数据。
  5. 参数5:启动选项
    CreateThread 的倒数第二个参数 dwCreationFlags(启动选项) 有两个可选值:
    0: 线程建立后立即执行入口函数;
    CREATE_SUSPENDED: 线程建立后会挂起等待。
  6. 参数6:输出线程ID
    CreateThread 的最后一个参数是 “线程的 ID”;既然可以返回句柄, 为什么还要输出这个 ID? 原因是:
    (1) 线程的 ID 是唯一的; 而句柄可能不只一个, 譬如可以用 GetCurrentThread 获取一个伪句柄、可以用 DuplicateHandle 复制一个句柄等等。
    (2) ID 比句柄更轻便.在主线程中 GetCurrentThreadId、MainThreadID获取的都是主线程的 ID。

1.3 多进程与多线程并发

一、多进程并发

 使用多进程并发是将一个应用程序划分为多个独立的进程(每个进程只有一个线程),这些独立的进程间可以互相通信,共同完成任务。由于操作系统对进程提供了大量的保护机制,以避免一个进程修改了另一个进程的数据,使用多进程比使用多线程更容易写出相对安全的代码。但是这也造就了多进程并发的两个缺点:
 (1)在进程间的通信,无论是使用信号、套接字,还是文件、管道等方式,其使用要么比较复杂,要么就是速度较慢或者两者兼而有之。
 (2)运行多个进程的开销很大,操作系统要分配很多的资源来对这些进程进行管理。
 当多个进程并发完成同一个任务时,不可避免的是:操作同一个数据和进程间的相互通信,上述的两个缺点也就决定了多进程的并发并不是一个好的选择。所以就引入了多线程的并发。

二、多线程并发

 多线程并发指的是在同一个进程中执行多个线程。
 优点:有操作系统相关知识的应该知道,线程是轻量级的进程,每个线程可以独立的运行不同的指令序列,但是线程不独立的拥有资源,依赖于创建它的进程而存在。也就是说,同一进程中的多个线程共享相同的地址空间,可以访问进程中的大部分数据,指针和引用可以在线程间进行传递。这样,同一进程内的多个线程能够很方便的进行数据共享以及通信,也就比进程更适用于并发操作。
 缺点:由于缺少操作系统提供的保护机制,在多线程共享数据及通信时,就需要程序员做更多的工作以保证对共享数据段的操作是以预想的操作顺序进行的,并且要极力的避免死锁(deadlock)。
 由于以上原因,我们在编程中应当经常采用多线程来解决编程问题,尽量避免创建新的进程。

2 线程同步

2.1 一个经典的线程同步问题

 多线程编程中,如果多个线程需要访问共享资源,就需要进行线程间的同步处理。那么什么是线程同步呢?线程同步是指当多个线程共享同一个资源,不会受到其他线程的干扰;或者说,线程同步指的是线程之间“协同”,即线程之间按照规定的先后次序运行。
 线程同步的概念和其他“同步”概念不太一致,请区分线程同步和下面两个同步概念:
  设备同步:在不同的设备之间规定一个共同的参考时间
  数据库/文件同步:在不同的数据库之间保持数据一致
 下面,我们创建一个多线程的程序,看看一个有趣的问题.

#include <iostream>
#include<windows.h>

DWORD WINAPI Fun1Proc(__in  LPVOID lpParameter);
DWORD WINAPI Fun2Proc(__in  LPVOID lpParameter);

int iIndx = 0;
int iTickets = 100;

//创建进程的时候,系统会自动创建进程的第一个线程,成为主线程,也就是
//执行main函数的进程
int main()
{
	HANDLE hThread1;
	HANDLE hThread2;

	//创建线程
	hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
	hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);

	while (true)
		if (iTickets > 0)
			Sleep(100);
		else
		{
			getchar();
			return 0;
		}
}

//
DWORD WINAPI Fun1Proc(__in  LPVOID lpParameter)
{
	while (true)
	{
		if (iTickets > 0)
			std::cout << "thread1 sell tickets:" << iTickets-- << std::endl;
		else
			break;
	}

	return 0;
}

DWORD WINAPI Fun2Proc(__in  LPVOID lpParameter)
{

	while (true)
	{
		if (iTickets > 0)
			std::cout << "thread2 sell tickets:" << iTickets-- << std::endl;
		else
			break;
	}
	return 0;
}

 首先,在main函数中我们创建了两个线程,线程的入口函数分别是Fun1Proc和Fun2Proc,在调用CreateThread函数时,第5个参数设置为0,表明线程立即执行。
 这里需要注意一点的是,我们在main函数中调用了CloseHandle;可能有人要问,为什么刚刚创建了线程,现在又关闭了呢?原因是CloseHandle并没有终止新创建的线程的执行,而仅仅表示主进程对新创建的线程的引用不感兴趣,因此将它关闭。另一方面,当关闭该句柄时,系统会递减该线程内核对象的使用计数。当创建的这个新线程执行完毕以后,系统也会递减该线程内核对象的使用计数。当使用计数为0时,系统就会释放该线程内核对象。如果没有关闭线程句柄,系统会一致保持着对线程内核对象的引用,这样,即使该线程执行完毕,它的引用计数仍不会为0。这样该线程内核对象也就不会被释放,只有等到进程终止时,系统才会清理这些残留的对象。
 运行这段代码,结果如下:
在这里插入图片描述
 在图中,竟然出现了线程2卖了倒数第14张票,紧接着线程1卖了倒数第15张票的情况,这说明两个线程函数在访问iTickets全局变量时,出现了同步问题。具体来说:当线程1卖倒数第15张票的时候,正准备要打印到屏幕上;然后此时系统将时间片交给了线程2,线程2卖了倒数第14张票,并且顺利打印;恰在此时,系统又将流程切换到了流程1,流程1继续打印卖了倒数第15张票这个信息。
 以上问题实际上是经典的线程同步问题,我们要让“线程之间“协同”,即线程之间按照规定的先后次序运行”,否则对共享资源的访问就可能出错。

2.2 利用互斥对象实现线程同步

 为了解决上述线程同步问题,我们可以使用互斥对象。
 互斥对象(mutex)属于内核对象,它能确保线程拥有单个资源的互斥访问权。互斥对象包含一个使用数量,一个线程ID和一个计数器。其中ID用于标识系统中哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

一、创建互斥对象

 创建互斥对象的函数为

HANDLE WINAPI CreateMutex(
__in_opt LPSECURITY_ATTRIBUTES lpMutexAttributes,
__in BOOL bInitialOwner,
__in_opt LPCTSTR lpName
);

 lpMutexAttributes:可以给该对象传递NULL值,让互斥对象使用默认的安全性。
 bInitialOwner:BOOL类型,指定互斥对象的初始拥有者。如果为真,则创建这个互斥对象的线程获得该对象的所有权;否则,该线程将不获得所创建的互斥对象的所有权。
 lpName是互斥对象的名称,如果参数为NULL,则创建了一个匿名对象。

 在互斥对象中,有一个重要概念:如果所有线程都不拥有互斥对象,那么就称互斥对象处于有信号状态;但凡某个线程拥有了互斥对象的所有权,就称互斥对象处于无信号状态。

二、获得互斥对象所有权

 线程必须主动请求互斥对象的使用权才可能获得该所有权,这可以通过下面函数实现

DWORD WINAPI WaitForSingleObject(
  __in  HANDLE hHandle,
  __in  DWORD dwMilliseconds
);

 hHandle:所请求对象的句柄。一旦互斥对象处于有信号状态,则该函数就返回。如果互斥对象始终处于无信号的状态,则该函数就会一直等待,这样就会暂停线程的执行。
 dwMilliseconds:指定的等待时间间隔,以毫秒为单位。如果指定的时间间隔已过,即使所请求的对象仍处于无信号状态,WaitForSingleObject函数也会返回。如果此参数设置为0,那么函数将测试该对象的状态并立刻返回;如果此参数设置为INFINITE,则该函数会永远等待,直到等待的对象处于有信号状态才会返回。

三、释放互斥对象的所有权

 当线程对共享资源访问结束后,应释放该互斥对象的所有权,也就是让互斥对象处于有信号状态,这可以通过下面函数实现

BOOL WINAPI ReleaseMutex( __in  HANDLE hMutex);

 ReleaseMutex函数只有一个HANDLE类型的参数,即需要释放的互斥对象的句柄。该函数的返回值是BOOL类型,如果函数调用成功,返回非0值,否则返回0值。
 另外需要注意,对互斥对象来讲,哪个线程拥有所有权,哪个线程才能释放这个对象。比如说A线程拥有互斥对象,你就不能在B线程内释放这个对象。另外操作系统一旦发现某个线程已经终止,它就会自动将该线程所拥有的互斥对象的线程ID设为0,并将其计数归0。

四、如何利用互斥对象实现线程同步

 基本思路是这样的:在程序中创建一个全局的互斥对象(当然,你可以使用单例模式,将全局对象封装在函数中,转为局部静态变量),在每个线程中,如果有需要访问的共享资源,就在访问共享资源之前通过WaitForSingleObject获得互斥对象的所有权,在访问之后,再用ReleaseMutex释放互斥对象的所有权。这样,每个线程在访问共享资源时,都会“排斥”其他线程获得互斥对象所有权,只有等这个线程访问完毕后,下一个线程才能获得互斥对象所有权,才能进来访问共享资源。这样,也就实现了线程同步了。

2.3 利用互斥对象的代码实例

 接下来,让我们看看利用互斥对象的具体使用例程

#include <iostream>
#include<windows.h>

DWORD WINAPI Fun1Proc(__in  LPVOID lpParameter);
DWORD WINAPI Fun2Proc(__in  LPVOID lpParameter);


int iIndx = 0;
int iTickets = 100;
HANDLE hMutex;

//创建进程的时候,系统会自动创建进程的第一个线程,成为主线程,也就是
//执行main函数的进程
int main()
{
	HANDLE hThread1;
	HANDLE hThread2;

	//创建互斥对象
	hMutex = CreateMutex(NULL, FALSE, NULL);

	//创建线程
	hThread1 = CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);
	hThread2 = CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);
	CloseHandle(hThread1);
	CloseHandle(hThread2);

	while (true)
	{
		if (iTickets > 0)
			Sleep(100);
		else
		{
			getchar();
			return 0;
		}
	}
}

//
DWORD WINAPI Fun1Proc(__in  LPVOID lpParameter)
{
	while (true)
	{
		WaitForSingleObject(hMutex, INFINITE);
		if (iTickets > 0)
		{
			std::cout << "thread1 sell tickets:" << iTickets-- << std::endl;
		}
		else
			break;
		ReleaseMutex(hMutex);
	}

	return 0;
}

DWORD WINAPI Fun2Proc(__in  LPVOID lpParameter)
{

	while (true)
	{
		WaitForSingleObject(hMutex, INFINITE);
		if (iTickets > 0)
		{
			std::cout << "thread2 sell tickets:" << iTickets-- << std::endl;
		}
		else
			break;
		ReleaseMutex(hMutex);
	}
	return 0;
}

 在上述main函数中,我们首先用CreateMutex创建了互斥对象,请注意其第二个参数为FALSE,所以主线程不拥有该对象。然后,在每个线程函数中都使用了WaitForSingleObject\ReleaseMutex完成了各线程对共享资源的同步访问要求。我们来看结果,同步问题已经解决。
在这里插入图片描述
 下一节,我们将介绍:Windows中多线程的基础知识——2事件对象。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

模糊测试面面观 | 模糊测试是如何准确定位问题的?

​前几期我们介绍了常见的模糊测试工具、模糊测试的优势及异常发掘&#xff0c;本期我们就来聊一下模糊测试是如何准确定位问题的。我们知道模糊测试是一种通过向目标系统提供非预期的“坏数据”输入并监视异常结果来发现软件漏洞的方法&#xff0c;能否准确定位问题是模糊测试…

Elasticsearch Head的使用

目录 概述一、安装 Elasticsearch Head二、解压文件三、安装Elasticsearch Head依赖四、启动 Elasticsearch Head五、修改Elasticsearch Head启动端口号六、使用 Elasticsearch Head注意事项 概述 Elasticsearch Head 是一个用于管理和监控 Elasticsearch 集群的 Web 界面工具…

Python Flask Web开发三:数据表的字段增加和删除

前言 在实际的开发中&#xff0c;数据表中的字段的增加和删除是很正常的操作&#xff0c;在运营的不断提需求下&#xff0c;这个修改的频率是很高的&#xff0c;那么在flask中如何进行字段的增加和删除呢&#xff1f;下面我来给大家讲讲 一、创建迁移脚本 使用数据库迁移工具…

从0开始学go第四天

模板继承 继承根模板&#xff0c;重新定义“块模板” 【Go Web开发系列教程】07-Go模板继承_哔哩哔哩_bilibili 解析模板时&#xff0c;base模板要在前 渲染模板时&#xff1a; 要用ExecuteTemplate&#xff0c;而不是Excute 模板补充&#xff1a;Go语言标准库之http/templ…

免费在线行为验证,保护你的账号安全

前言 忘记繁琐的验证码吧&#xff01;免费在线行为验证服务&#xff0c;通过滑动图片、滑动拼图和文字点选等方式&#xff0c;确保只有真正的人类用户能够访问。 前端代码 <script src"https://cdn6.kgcaptcha.com/captcha.js"></script> <script&g…

从0到1实现播放控制器

这系列文章主要讲诉如何从0到1使用QT实现带时间显示、滚动字幕等的自定义配置视频播放控制器。平时我们乘坐地铁经常看到各条线的播放控制器都大同小异。其实都是通过QT等界面开发软件来实现的。 在具体开发之前&#xff0c;需要明确我们需要做什么&#xff1f; 1. 开发一个可…

Java中级面试题记录(三)

1.职业规划&#xff1f; 2.每家公司离职原因&#xff1f; 3.SpringCloud用到了哪些组件&#xff1f; GateWayNacosOpenFeignSeataHystrix 4.PG和Mysql的区别&#xff1f; 5.两种数据库的存储区别&#xff1f; 6.MySQL索引了解的内容&#xff1f; 一口气搞定索引的所有知识…

PCL入门(二):初识点云数据

目录 1. 点云数据2. 对点云数据的简单操作3. 结果 1. 点云数据 在pcl里面&#xff0c;定义了很多点云数据类型&#xff0c;比如PointXYZ、PointXYZI、PointXYZRGBA等等&#xff0c;每一个都可以看做是点云的一个点的数据。而整个点云的数据类型被定义为PointCloud。 以pcl::P…

19-springcloud(上)

一 微服务架构进化论 单体应用阶段 (夫妻摊位) 在互联网发展的初期&#xff0c;用户数量少&#xff0c;一般网站的流量也很少&#xff0c;但硬件成本较高。因此&#xff0c;一般的企业会将所有的功能都集成在一起开发一个单体应用&#xff0c;然后将该单体应用部署到一台服务器…

Build阶段-Maven安装配置

构建Java项目的工具一般有两种选择&#xff0c;一个是Maven&#xff0c;一个是Gradle。 这里我们选择Maven作为项目的编译工具。 具体安装Maven流程不做阐述&#xff0c;但是需要确保配置好Maven仓库私服以及JDK编译版本

财报解读:营收利润增长稳健,百果园产业链转型正在进行时?

百果园的精品水果之路越走越踏实。 近日&#xff0c;百果园发布了截至2023年6月30日止六个月的中期业绩报告。财报显示&#xff0c;2023年上半年&#xff0c;集团收入为62.94亿元人民币&#xff0c;同比增长6.4%&#xff1b;毛利为7.13亿元人民币&#xff0c;同比增长5.2%&…

Python列表排序

介绍一个关于列表排序的sort方法&#xff0c;看下面的案例&#xff1a; """ 列表的sort方法来对列表进行自定义排序 """# 准备列表 my_list [["a", 33], ["b", 55], ["c", 11]]# 排序&#xff0c;基于带名函数 …

sklearn中的数据集使用

导库 from sklearn.datasets import load_iris 实现 # 加载数据集 iris load_iris() print(f查看数据集&#xff1a;{iris}) print(f查看数据集的特征&#xff1a;{iris.feature_names}) print(f查看数据集的标签&#xff1a;{iris.target_names}) print(f查看数据集的描述…

【C++】atoi函数和itoa函数无废话极简概述

atoi和itoa这两个函数头文件均为<stdlib.h> atoi atoi全称&#xff1a;array to int&#xff0c;可以把字符串转换为整型 int atoi(const char *nptr); 使用演示&#xff1a; #1 char str[5]; cin>>str; int aatoi(str);#2 int a; aatoi("123");//a…

vue2中实现 TDesign 树形懒加载

之前我有写过Element ui的树形懒加载 其主要是通过load函数来实现 而TDesign也是照虎画猫 他也是主要靠load 我们先来看一个基本的组件 <template><t-tree :data"items" :load"load" /> </template><script lang"jsx">…

mysql忘记root密码如何解决?

第一步&#xff1a;首先右键此电脑打开管理器&#xff0c;查看mysql是否运行 第二步&#xff1a;用管理员模式打开命令框 输入net stop mysql暂停mysql运行 net stop mysql 然后输入下面指令 mysql --console --skip-grant-tables --shared-memory 显示如下 第三步&…

助力智能辅助识别,基于轻量级卷积神经网络mobilenet开发构建结直肠息肉识别系统

AI与医学领域数据的结合早已是一个热门的方向&#xff0c;基于深度学习技术来开发辅助智能识别和检测模型对于疾病的高效智能化诊断有着重要的指导意义&#xff0c;这里本文的主要思想就是想要基于轻量级的CNN模型来尝试开发构建息肉识别系统&#xff0c;后续项目中会需要基于此…

darknet识别(某验)文字点选验证码

今天介绍darknet识别文字点选验证码&#xff0c; Darknet is an open source neural network framework written in C and CUDA. darknet是基于yolo算法的神经网络框架。 废话少说先热热身 平台是Ubuntu20&#xff0c;首先要安装NVIDIA驱动 1、安装驱动 NVIDIA GeForce 驱动…

FPGA基本算术运算

FPGA基本算术运算 FPGA基本算术运算1 有符号数与无符号数2 浮点数及定点数I、定点数的加减法II、定点数的乘除法 3 仿真验证i、加减法验证ii、乘除法验证 FPGA基本算术运算 FPGA相对于MCU有并行计算、算法效率较高等优势&#xff0c;但同样由于没有成型的FPU等MCU内含的浮点数运…

11 滑动窗口最大值

滑动窗口最大值 题解1 优先队列&#xff08;大顶堆&#xff09;&#xff08;双循环没过&#xff09;改进后 题解2 单调队列deque&#xff08;设计同labuladong&#xff09;标准题解&#xff08;同接雨水&#xff0c;deque存下标&#xff09; 题解1 优先队列&#xff08;大顶堆&…