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

目录

  • 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;elsebreak;}return 0;
}DWORD WINAPI Fun2Proc(__in  LPVOID lpParameter)
{while (true){if (iTickets > 0)std::cout << "thread2 sell tickets:" << iTickets-- << std::endl;elsebreak;}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;}elsebreak;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;}elsebreak;ReleaseMutex(hMutex);}return 0;
}

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

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

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

相关文章

JavaScript-----对象(创建对象、数组与字符串)

目录 前言&#xff1a; 1. JavaScript创建对象 1.1 对象的创建 1.2 对象的调用 1.3 for-in循环语句 2.内置对象 2.1 Array&#xff08;数组&#xff09;对象 属性和方法 2.2 String&#xff08;字符串&#xff09;对象 属性和方法 2.3 Math对象 2.4 日期对象 前言&a…

SIEM 中不同类型日志监控及分析

安全信息和事件管理&#xff08;SIEM&#xff09;解决方案通过监控来自网络的不同类型的数据来确保组织网络的健康安全状况&#xff0c;日志数据记录设备上发生的每个活动以及整个网络中的应用程序&#xff0c;若要评估网络的安全状况&#xff0c;SIEM 解决方案必须收集和分析不…

mysql数据库使用技巧整理

查看当前数据库已建立的client连接 mysql中执行 -- 查看数据库允许的最大连接数&#xff0c;不是实时正在使用的连接数 SHOW VARIABLES LIKE max_connections; mysql中执行 -- 查看当前数据库client的连接数 SHOW STATUS LIKE Threads_connected; mysql中执行 -- 查看具…

微服务-gateway基本使用

文章目录 一、前言二、gateway网关1、什么是微服务网关&#xff1f;2、微服务架构下网关的重要性2.1、没有网关2.2、有网关 3、gateway的功能4、gateway实战4.1、依赖配置4.2、添加网关配置4.3、添加网关启动类4.4、查看项目是否启动成功4.5、验证路由配置是否正确 三、总结 一…

行业追踪,2023-09-04

自动复盘 2023-09-04 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

2023年高教社杯 国赛数学建模思路 - 案例:感知机原理剖析及实现

文章目录 1 感知机的直观理解2 感知机的数学角度3 代码实现 4 建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 感知机的直观理解 感知机应该属于机器学习算法中最简单的一种算法&#xff0c;其…

用Qt自制一个小闹钟

小闹钟 功能 当按下启动按钮时&#xff0c;停止按钮可用&#xff0c;启动按钮不可用&#xff0c;闹钟无法设置&#xff0c;无法输入自定义内容 当按下停止按钮时&#xff0c;暂停播报&#xff0c;启动按钮可用&#xff0c;闹钟可以设置&#xff0c;可以输入自定义内容 .pro文…

分布式环境集成JWT(Java Web Token)

目录 一&#xff0c;说明&#xff1a;二&#xff0c;Token、Session和Cookie比较三&#xff0c;Spring Boot项目集成JWT1&#xff0c;引入依赖2&#xff0c;Token工具类3&#xff0c;定义拦截器4&#xff0c;注册拦截器5&#xff0c;编写登录代码6&#xff0c;测试 四&#xff…

Vue 2 nextTick方法|异步更新|事件循环

1 nextTick的用处 vm.$netTick的作用是将回调延迟到下次DOM更新周期之后执行。 它接受一个回调函数作为参数。 其实&#xff0c;在我们更新数据状态后&#xff0c;是不会立马渲染的&#xff0c;你不能即刻获取到新的DOM&#xff1a; <!DOCTYPE html> <html><…

[论文笔记]ESIM

引言 这是经典论文Enhanced LSTM for Natural Language Inference的笔记。 本篇论文文是建立在自然语言推理(Natural Language Inference,NLI)任务上的。提出了简单的通过基于LSTM的序列推理模型效果到达了当时的SOTA水平。同时基于该模型,在局部推理建模层和推理组合层使用了…

嵌入式岗位笔试面试专栏 - 岗位介绍

文章目录 一、嵌入式岗位的分类二、热门领域及公司三、发展前景四、技能要求沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本篇我们将讲解嵌入岗位的工作职责 。 一、嵌入式岗位的分类 嵌入式软件工程师大致可以分为两种类型: 应用开发工程师驱动开发工程师应用工程…

安全基础 --- 原型链污染

原型链 大部分面向对象的编程语言&#xff0c;都是通过“类”&#xff08;class&#xff09;实现对象的继承。传统上&#xff0c;JavaScript 语言的继承不通过 class&#xff0c;而是通过“原型对象”&#xff08;prototype&#xff09;实现 1、prototype 属性的作用 JavaScri…

蝶形运算法

蝶形运算法是一种基于FFT&#xff08;Fast Fourier Transform&#xff09;算法的计算方法&#xff0c;其基本思想是将长度为N的DFT分解成若干个长度为N/2的DFT计算&#xff0c;并通过不断的合并操作得到最终的结果。该算法也称为“蝴蝶算法”&#xff0c;因为它的计算过程中需要…

Git 版本回退 超神步骤

Git 版本回退 一. 背景 多版本分支开发&#xff0c;合并版本问题太多&#xff0c;需要回滚到某次版本。我的git客服端工具是 sourcetree 二.操作步骤 2.1 切到当前需要回退版本的分支 2.2 右击需要具体某一个分支&#xff0c;这个分支就是你想切到的分支版本&#xff0c;具体…

Spark 环境安装与案例演示

Spark 环境安装 一、准备工作 1、hadoop成功安装 2、防火墙关闭 二、解压安装 1、上传 spark 安装包到/tools 目录&#xff0c;进入 tools 下&#xff0c;执行如下命令&#xff1a; tar -zxvf spark-2.1.0-bin-hadoop2.7.tgz -C /training/由于 Spark 的脚本命令和 Hadoop…

12 最小覆盖串

最小覆盖串 题解1 滑窗双指针模板&#xff08;labuladong&#xff0c;高效好套&#xff0c;length版&#xff09; 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串&#xff0c;则返回空字符串 "&quo…

wpf从cs代码创建简单3D物体和3D Tools研究

前面已经说了&#xff0c;WPF项目中引入3DTools dll之后&#xff0c;在xaml中加入它的命名空间&#xff0c; xmlns:tools"clr-namespace:_3DTools;assembly3DTools" 把<Viewport3D>标签包含在<tools:TrackballDecorator>标签之中&#xff0c;就可以用鼠…

ping: www.baidu.com: Name or service not known 写了DNS还是不行

环境描述&#xff1a;ESXI平台上&#xff0c;一台Centos7虚拟主机。 问题描述&#xff1a;平台上的其他的虚拟机可以正常ping通&#xff0c;就这台ping IP地址可以通&#xff0c;ping域名解析失败。 排查过程&#xff1a; 1、检查网卡配置文件和/etc/resolv.conf配置文件是否…

【SQL学习笔记】关系模型与查询和更新数据

一、关系模型 1.1 主键 主键是关系表中记录的唯一标识。主键的选取非常重要&#xff1a;主键不要带有业务含义&#xff0c;而应该使用BIGINT自增或者GUID类型。主键也不应该允许NULL。 可以使用多个列作为联合主键&#xff0c;但联合主键并不常用。 1.2 外键 FOREIGN KEY …

0010Java程序设计-springboot+vue影院售票系统设计与实现

摘 要目 录系统实现开发环境 摘 要 看电影已经成为了人们生活中不可缺少的一部分&#xff0c;电影院售票及管理系统是电影院的日常管理及售票任务的核心&#xff0c; 在电影院中&#xff0c; 工作人员并非只是放映电影&#xff0c; 还有诸如票房统计、影片放映、影片场次安排、…