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

news2024/11/19 13:15:24

目录

  • 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完成了各线程对共享资源的同步访问要求。我们来看结果,同步问题已经解决。
在这里插入图片描述

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

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

相关文章

B093-springsecurity整合jwt和RSA

目录 前后端分离后springsecurity核心filter的应用场景介绍JWT令牌的组成部分JWT案例导包TestJwt RSARsaUtilsTestRSA分析图 JWTRSA导包JwtUtilsTestRSAJWT 完善spring-security整合后且不连数据库的代码案例流程分析图 前后端分离后springsecurity核心filter的应用场景介绍 账…

汽车电子系统网络安全解决方案

声明 本文是学习GB-T 38628-2020 信息安全技术 汽车电子系统网络安全指南. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 汽车电子系统网络安全范围 本标准给出了汽车电子系统网络安全活动框架&#xff0c;以及在此框架下的汽车电子系统网络安全活动…

redis 5.0.x 部署

PS&#xff1a;对于使用者来说&#xff0c;Redis5.0和4.0都是一样的&#xff0c;但是redis 4.0的集群部署需要额外安装ruby的东西&#xff0c;5.0中则集成到了redis-cli&#xff0c;部署起来更方便 1.1 安装Redis 本章基于CentOS 7.9.2009编写而成&#xff0c;由于Linux发行版…

Shotcut for Mac:一款强大而易于使用的视频编辑器

随着数码相机的普及&#xff0c;视频编辑已成为我们日常生活的一部分。对于许多专业和非专业用户来说&#xff0c;找到一个易于使用且功能强大的视频编辑器是至关重要的。今天&#xff0c;我们将向您介绍Shotcut——一款专为Mac用户设计的强大视频编辑器。 什么是Shotcut&…

C#-SQLite-使用教程笔记

微软官网资料链接&#xff08;可下载文档&#xff09; 教程参考链接&#xff1a;SQLite 教程 - SQLite中文手册 项目中对应的system.dat文件可以用SQLiteStudio打开查看 参考文档&#xff1a;https://d7ehk.jb51.net/202008/books/SQLite_jb51.rar 总结介绍 1、下载SQLiteS…

【GPT引领前沿】GPT4技术与AI绘图

推荐阅读&#xff1a; 1、遥感云大数据在灾害、水体与湿地领域典型案例实践及GPT模型应用 2、GPT模型支持下的Python-GEE遥感云大数据分析、管理与可视化技术 GPT对于每个科研人员已经成为不可或缺的辅助工具&#xff0c;不同的研究领域和项目具有不同的需求。例如在科研编程…

音视频技术开发周刊 | 309

每周一期&#xff0c;纵览音视频技术领域的干货。 新闻投稿&#xff1a;contributelivevideostack.com。 腾讯云音视频及边缘平台专场邀你一起见证“连接”的力量 9月7日&#xff0c;腾讯全球数字生态大会之腾讯云音视频及边缘平台专场即将开启&#xff01;本次专场将重点分享腾…

最小生成树Kruskal、Prim算法C++

什么是最小生成树 连通图&#xff1a; 在无向图中&#xff0c;若从顶点v1到顶点v2有路径&#xff0c;则称顶点v1和顶点v2是连通的。如果图中任意一对顶点都是连通的&#xff0c;则称此图为连通图。 生成树&#xff1a; 一个连通图的最小连通子图称作为图的生成树。有n个顶点的…

OceanBase 里的 schema 是什么?

李博洋 OceanBase 技术部研发工程师。 OceanBase 开源社区里经常会看到一些类似于 “ schema 是什么” 的疑问&#xff1a; 很多同学经常会误以为在 OceanBase 里&#xff0c;schema 只是 database 的同义词&#xff0c;这次分享就从 schema 是什么这个问题稍微展开聊一下。 首…

【51单片机实验笔记】声学篇(一) 蜂鸣器基本控制

目录 前言硬件介绍PWM基础蜂鸣器简介 原理图分析蜂鸣器驱动电路 软件实现蜂鸣器短鸣蜂鸣器功能封装 总结 前言 蜂鸣器在生活中的应用实则相当广泛。通过本章你将学会制造噪声 &#xff08;笑~&#xff09;你将学会驱动它们&#xff0c;并发出响声。 硬件介绍 PWM基础 占空比…

计算机竞赛 基于深度学习的人脸专注度检测计算系统 - opencv python cnn

文章目录 1 前言2 相关技术2.1CNN简介2.2 人脸识别算法2.3专注检测原理2.4 OpenCV 3 功能介绍3.1人脸录入功能3.2 人脸识别3.3 人脸专注度检测3.4 识别记录 4 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习的人脸专注度…

【CPU】常见术语解释

interrupt service routine&#xff0c;ISR&#xff1a;中断服务程序。 中断&#xff1a;指当CPU正在处理某件事情时&#xff0c;外部发生的某一事件&#xff08;如一个电平的变化&#xff0c;一个脉冲沿的发生或 定时器计数溢出等&#xff09;请求CPU迅速去处理&#xff0c;于…

select多选回显问题 (取巧~)

要实现的效果&#xff1a; 实际上select选择框&#xff0c;我想要的是数组对象&#xff0c;但是后端返回来的是个字符串。 以下是解决方法&#xff1a; 以上是一种简单的解决方法~ 也可以自己处理数据。

【聚类】DBCAN聚类

OPTICS是基于DBSCAN改进的一种密度聚类算法&#xff0c;对参数不敏感。当需要用到基于密度的聚类算法时&#xff0c;可以作为DBSCAN的一种替代的优化方案&#xff0c;以实现更优的效果。 原理 基于密度的聚类算法&#xff08;1&#xff09;——DBSCAN详解_dbscan聚类_root-ca…

分类算法系列⑥:随机森林

目录 集成学习方法之随机森林 1、集成学习方法 2、随机森林 3、随机森林原理 为什么采用BootStrap抽样 为什么要有放回地抽样 4、API 5、代码 代码解释 结果 6、随机森林总结 &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家…

Llama-7b-hf和vicuna-7b-delta-v0合并成vicuna-7b-v0

最近使用pandagpt需要vicuna-7b-v0&#xff0c;重新过了一遍&#xff0c;前段时间部署了vicuna-7b-v3&#xff0c;还是有不少差别的&#xff0c;transforms和fastchat版本更新导致许多地方不匹配&#xff0c;出现很多错误&#xff0c;记录一下。 更多相关内容可见Fastchat实战…

Python小知识 - 【Python】如何使用Pytorch构建机器学习模型

【Python】如何使用Pytorch构建机器学习模型 机器学习是人工智能的一个分支&#xff0c;它的任务是在已有的数据集上学习&#xff0c;最终得到一个能够解决新问题的模型。Pytorch是一个开源的机器学习框架&#xff0c;它可以让我们用更少的代码构建模型&#xff0c;并且可以让模…

docker 安装rabbitmq

前提&#xff1a;安装好docker docker安装_Steven-Russell的博客-CSDN博客 centos7安装docker_centos7 docker 安装软件_Steven-Russell的博客-CSDN博客 1、启动docker systemctl start docker 2、下载镜像 // 可以先search查询一下可用镜像&#xff0c;此处直接下载最新版本…

LinkedList(3):并发异常

1 LinkedList并发异常 package com.example.demo;import java.util.Iterator; import java.util.LinkedList;public class TestLinkedList {public static void main(String[] args) {LinkedList linkedList new LinkedList(); //双向链表linkedList.add(11);linkedList.add(…

【ES6】require、export和import的用法

在JavaScript中&#xff0c;require、export和import是Node.js的模块系统中的关键字&#xff0c;用于处理模块间的依赖关系。 1、require&#xff1a;这是Node.js中引入模块的方法。当你需要使用其他模块提供的功能时&#xff0c;可以使用require关键字来引入该模块。例如&…