注:面试过程中整理的学习资料,如有侵权联系我即刻删除。
目录
是否了解进程、线程、程序,以及这三者的区别
什么是虚拟内存
IO同步、异步、阻塞、非阻塞
异步通信的通知方法
进程线程的工作状态
线程的状态转换过程
PV操作
IO复用
IO多路复用的使用场景
IO多路复用之seect、poll、epoll
Windows下有哪些实现多线程的函数
死锁是什么意思?
死锁发生的必要条件
如何避免死锁?
线程之间是如何进行通信的?
什么是多线程同步,线程同步与线程互斥的区别?
如何实现多线程同步?或者问操作系统中有哪些锁?
互斥量mutex和信号量的区别
哪些是线程安全的?哪些是线程不安全的?
如何创建进程?如何关闭进程?
windows的消息机制
一个消息从产生到被窗口响应的过程(windows详细消息机制
Windows消息有哪些
一个消息发送到窗口的三种方式
Sendmessage和Postmessage的详细区别
消息的接收
窗口过程是什么
MFC的消息机制
MFC消息如何添加
MFC中一个自定义消息如何实现
windows消息范围和意义
Windows sdk程序的结构
MFC程序的启动过程
Windows应用程序的启动过程
Windows下两个进程之间如何通信
Linux下两个进程之间如何通信
两个进程如果指向同一个地址,是不是在物理上是同一块区域
多线程中堆和栈是公有的还是私有的?
全局变量i = 0,i++在两个线程分别执行100次,最大值和最小值分别多少
i=100,两个线程i--,均执行50次,可能值是多少?
操作系统OS
逻辑地址、物理地址、虚拟地址的关系
程序只是一组指令的有序集合,不能单独执行,只有将程序加载到内存中,系统为它分配资源后才能执行,这种执行的程序就叫进程。进程就是一段程序的执行过程,所以程序就是进程运行的静态描述文本,而进程则是程序在系统上顺序执行时的动态活动。
但是进程有两个主要的缺点,一个是进程在同一时间只能干一件事情,第二个是进程在执行过程中如果因为某些原因阻塞了,那么整个进程就会挂起等待这个阻塞。线程就解决了这两个缺陷。
https://blog.csdn.net/woaigaolaoshi/article/details/51039505
虚拟内存是计算机系统内存管理的一种技术,它使得应用程序认为它拥有连续的可用的内存,而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上。用部分硬盘空间来充当内存。
< 同步与阻塞的不同 >
同步/异步关注的是消息的通知机制。
同步,就是发出一个功能调用时,在没有得到结果之前,该调用不能返回,需要等待。比如SendMessage函数,发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回,当对方处理完毕以后,该函数才把消息处理函数所返回的值返回给调用者。
也就是调用者在主动等待这个调用结果。
异步,不需要等待,直接返回,调用者不能立刻得到调用结果。通过状态、通知、回调函数来通知调用者。
同步/异步关心的是消息通信机制。
阻塞/非阻塞关心的是等待调用结果的状态。
阻塞/非阻塞与是否同步/异步无关。
阻塞调用是指调用结果返回之前,当前线程会被挂起。比如socket的recv函数就是阻塞调用,在没有数据的时候一起被挂起。
非阻塞调用是指不能立刻得到结果之前,函数不会阻塞当前线程,而是会立刻返回。
无论如何等待,只要在等待过程中,等待着除了等待消息之后什么都不能做,那就是阻塞的。表现在程序中也就是,代码一直阻塞在该函数调用处无法继续往下执行。
状态、通知、回调函数。
状态、通知、回调函数具体是指?
回调函数其实就是个函数指针,指向一个函数的入口地址。这是基于C编程的Windows SDK的技术,类的成员函数不能作为回调函数,因为隐含this指针,使得参数不匹配,但是可以用类的静态成员函数。
Sort中的compare就是回调函数。
进程线程的工作状态通常分为就绪、运行、阻塞三种状态。
进程三个状态之间的转换就是通过PV操作来控制的。
在线程运行过程中如果遇到了导致阻塞的事件,线程会放弃cpu的使用权,进入阻塞状态。等到这个阻塞解除之后,这个线程会再次进入就绪状态。
PV操作主要就是关于信号量的P操作、V操作。
P操作:使S=S-1,若S>=0,则该进程继续执行,否则该进程排入等待队列。
V操作:使S=S+1,若S<0, 表名有进程阻塞在该资源上,唤醒等待队列中的一个进程。
信号量的值S>=0表示可用资源的数目,执行一次p操作就是请求分配一个资源,S的值会-1,当S<0时,表示现在没有可用资源,S的绝对值表示现在等待这个资源的进程个数,必须等待别的进程释放资源。执行一次v操作就是释放一个资源,S的值会+1,如果S<0,说明现在有进程在等待该资源,就唤醒一个进程。S=0的时候表示资源刚好用完。
I/O多路复用就是把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的情况下可以同时处理多个客户端请求。
< 在一个客户和服务器通信时这没什么问题,当多个客户与服务器通信时,若服 务器阻塞于其中一个客户sockfd1,当另一个客户的数据到达套接字sockfd2时,服务器不能处理,仍然阻塞在read上;此时问题就出现了,不能及时处理另一个客户的服务,咋么办? I/O多路复用来解决。>
I/O多路复用:服务器同时监听这n个客户,当其中有一个发来消息时就从select的阻塞中返回,然后就调用 read 读取收到消息的sockfd,然后又循环回select阻塞;这样就不会因为阻塞在其中一个上而不能处理另一个客户的消息。
IO复用和多线程是两种解决单个服务器应对多个客户端同时IO请求阻塞问题的方案。
跟多线程相比较,线程切换需要切换到内核进行线程切换,需要消耗时间和资源,而I/O多路复用不需要切换线/进程,效率相对较高,特别是对高并发的应用,就是用I/O多路复用,故而性能极佳,但多线程编程逻辑和处理上比I/O多路复用简单,I/O多路复用处理起来较为复杂。
如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用
这三个都能监听操作符。
epoll只在Linux下有支持,select在各系统都可支持。
select的基本原理:
select 函数监视的文件描述符分3类,分别是writefds、readfds、和exceptfds。调用后select函数会阻塞,直到有描述符就绪(有数据 可读、可写、或者有except),或者超时(timeout指定等待时间,如果立即返回设为null即可),函数返回。当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。
select的缺点:
poll:
poll本质上和select区别不大。都是轮询的方法,poll没有最大连接数的限制,原因是它是基于链表来存储的。select和poll都需要在返回后,通过遍历文件描述符来获取已经就绪的socket。
缺点:
epoll:
相对于select和poll来说,epoll更加灵活,没有描述符限制。
优点:
1、没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口)。
2、效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。
< 只有活跃可用的FD才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll。>
3、内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。
总结:select和poll调用都返回整个用户注册的事件集合,所以应用程序索引就绪文件描述符的时间复杂度为O(n)。epoll内核检测到就绪的文件描述符时,将触发回调函数,回调函数就将该文件描述符上对应的事件插入内核就绪事件队列。内核最后在适当的时机将该就绪事件队列中的内容拷贝到用户空间。因此epoll_wait无须轮询整个文件描述符集合,所以其时间复杂度为O(1)。
CreateThread是操作系统提供的接口,AfxBeginThread和_beginthreadex是编译器对它的封装。
(1)CreateThread
第三个参数代表启动参数,也就是调用这个线程函数。倒数第二个参数是运行参数,为0的时候表示,线程一创建就启动,不为0就挂起,等待启动。如果这个地方传1,那么接下来可以调用ResumeThread(h1),来解除线程挂起状态,进入等待启动状态。
DWORD 是32位的无符号整型。
WINAPI是#define WINAPI _stdcall,是一个宏,表明后面这个函数的调用是标准调用约定。具体是关于堆栈的一些说明。Window API都采用这种调用约定。_stdcall表示参数的进栈顺序和标准c是一样的,但是采用的是自动清栈的方式。如果不显示说明的话那就是手动清栈。
如果在CreateThread创建的线程中调用如malloc、fopen这样的CRT函数,线程结束时不会正确清理函数中分配的内存,从而会导致内存泄露。
(2)_beginthreadex
我们通常选择用_beginthreadex来创建新线程,_beginthreadex内部调用了CreateThread函数,由于标准c运行库与多线程的矛盾,多个线程访问标准c运行库中的全局变量如errno和全局函数,会导致数据被覆盖的问题。_beginthreadex()函数在创建新线程时会分配并初始化一个_tiddata块。这个_tiddata块自然是用来存放一些需要线程独享的数据。事实上新线程运行时会首先将_tiddata块与自己进一步关联起来。然后新线程调用标准C运行库函数如strtok()时就会先取得_tiddata块的地址再将需要保护的数据存入_tiddata块中。这样每个线程就只会访问和修改自己的数据而不会去篡改其它线程的数据了。最后需要用_endthreadex函数来终结线程。_endthreadex中没有调用CloseHandle,所以还需自己写CloseHandle。
m_Thread = AfxBeginThread(AquisitionThread, this, 0, 0, NULL);
1、AfxBeginThread函数调用的函数要么是全局函数要么是类内的静态函数;
2、可以将类指针this作为参数传入调用函数,通过此指针就可以访问类中的成员变量和成员函数,否则被访问的成员变量和成员函数也必须是静态的。
AfxBeginThread是MFC中常用的创建线程的函数,内部是使用了_beginthreadex()函数的。调用AfxBeginThread(线程函数,this)就可以创建一个线程。完了之后用AfxEndThread或者是PostThreadMessage(m_nThreadID, WM_QUIT,0,0);给这个线程发送消息来结束线程。
总结应用场合如下:
CreateThread,线程中不使用CRT,不使用MFC库。
_beginthreadex,线程中使用CRT,不使用MFC库。
AfxBeginThread,线程中使用CRT,使用MFC库。
死锁就是两个或两个以上的进程被无限的阻塞,进程之间相互等待所需的资源,如果没有外力,这些线程无法向前推进。(对共享资源的竞争)
比如某计算机系统中只有一台打印机和一台输入设备,进程P1正占用输入设备,同时又提出使用打印机的请求,但此时打印机正被进程P2 所占用,而P2在未释放打印机之前,又提出请求使用正被P1占用着的输入设备。这样两个进程相互无休止地等待下去,均无法继续执行,此时两个进程陷入死锁状态。(两个进程各自占用资源又互相等待对方的资源,就陷入无限等待)
互斥、不剥夺、请求和保持、循环等待。
互斥:同一时间某一资源只能被同一进程占有。此时其他进程只能等待。
不剥夺:进程所获得的资源在未使用完毕之前不可以被别的进程强行夺走,只能由该进程主动释放。
请求和保持:进程已经保持了至少一个资源,又提出新的资源请求,但该资源已经被别的进程所占,此时请求进程被阻塞,但对自己已获得的资源保持不放。
循环等待:存在一种进程资源的循环等待链,链中每一个进程所获得的资源同时被下一个进程所请求。
https://blog.csdn.net/ls5718/article/details/51896159
多线程之间的通信就是 通过读写一个进程内的数据段(如全局变量)来实现通信的。然后我们可以通过临界区变量、事件变量、还有消息传递机制来实现。
线程同步:(大多数情况)是指在互斥的基础上,通过一些机制实现访问者对资源的有序访问。
线程互斥: 是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
临界区被初始化后,当程序进入临界区后便拥有临界区的所有权,其余线程无权进入只能等对方释放临界区之后,方可进入临界区拥有其所有权再对临界区进行操作。
InitializeCriticalSection()初始化临界区;
EnterCriticalSection()进入临界区;
LeaveCriticalSection()释放临界区所有权并离开临界区;
注意:上述是windows API中相关函数,CCriticalSection类是MFC中定义的临界区类,需要在MFC程序中使用(此程序为控制台程序无法使用MFC类),可以操作临界区,lock锁定临界区、unlock释放临界区。
临界区为依次访问,不能实现其中一个线程一释放临界区就会被另一个线程访问临界区!不能实现实时监听。
事件对象是指用户在程序中使用内核对象的有无信号状态实现线程的同步临界区被初始化后,当程序进入临界区后便拥有临界区的所有权,其余线程无权进入只能等对方释放临界区之后,方可进入临界区拥有其所有权再对临界区进行操作。
CreatEvent()创建并返回事件对象;
SetEvent()将指定的事件设置为有信号状态(有信号状态下其余线程可以访问);
ResetEvent()将指定的事件设置为无信号状态;
每个线程中WaitForSingleObject()函数等待事件状态。
注意:上述是Windows API函数,CEvent类是MFC实现事件对象的类,事件对象为立即访问,一旦事件对象被设置为有信号 立刻会被其余线程访问!能实现实时监听。
互斥对象还可以在进程间使用,在实现线程同步时包含一个线程ID和一个计数器,线程ID表示拥有互斥对象的线程,计数器表示该互斥对象被同一线程所使用次数。
CreatMutex()创建并返回互斥对象;
ReleaseMutex()释放互斥对象句柄;
WaitForSingleObject()对该对象进行请求。
注意:上述是Windows API函数,CMutex类是MFC中的互斥对象类。互斥对象为立即访问,一旦互斥对象被释放 立刻会被其它正在等待的线程访问!能实现实时监听。
线程函数myfun1如以下写:
互斥量需要解锁和加锁,在每个线程的开头都需要请求互斥量,最后都需要释放互斥量,只有当锁处于解锁状态时线程才能进入临界区。
信号量是用一个value来计数,进行pv操作,value大于0时,表示可用的资源数,value小于0时表示现在处于挂起线程的个数。
互斥量的加锁和解锁必须是同一线程对应执行,而信号量可以一个线程释放,另一个线程得到。
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。
线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
懒汉单例模式就是线程不安全的。
用CreateProcess函数,传入一个.exe的路径,用TerminateProcess来关闭进程,传入一个进程的句柄。
window消息机制定义:
消息,就是定义了一个事件,windows发出一个通知,告诉应用程序某个事情发生了,比如点击鼠标、按下按键等都会使windows发送一个消息给应用程序。
这个消息MSG是一个结构体,如下:
这个结构体中有句柄hwnd、消息号message、消息号我们项目中用的是WM_USER(0X0400) + 一个宏,然后wparam和lparam是作为调用哪个分支的标志,通常只是与消息相关的常量值。
wParam
通常是一个与消息有关的常量值,也可能是窗口或控件的句柄。
lParam
通常是一个指向内存中数据的指针。
当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由 Windows系统去进行处理。Windows系统则在适当的时机,从系统消息队列中取出一个消息,根据前面我们所说的MSG消息结构确定消息是要被送往那个窗口,然后把取出的消息送往创建窗口的线程的消息队列中。线程看到自己的消息队列中有消息,就从队列中取出来,通过操作系统发送到合适的窗口过程去处理。
主要有三类:窗口消息、命令消息和控件通知消息。
(ON_MESSAGE,ON_COMMAND, ON_NOTIFY)
WM_XXX,除WM_COMMAND之外,所有以WM_开头的消息都是窗口消息。
像CreateWindow、DestroyWindow和MoveWindow等都会激发窗口消息,包括单击鼠标也是一种窗口消息。通常,消息是从系统发送到窗口,或从一个窗口发送到另一个窗口。
< 使用postmessage时,我们就需要用ON_MESSAGE把要调用的函数和消息号WM_USER+X(宏)绑定在一起。>
是一种特殊的窗口消息,他用来处理从一个窗口发送到另一个窗口的用户请求,例如按下一个按钮,他就会向主窗口发送一个命令消息 。
是指,一个窗口内的子控件发生了一些事情,需要通知父窗口。通知消息只适用于标准的窗口控件如按钮、列表框、组合框、编辑框。
发送(sendmessage)、寄送(postmessage)和广播(boardmessage)。
< 不是十分急迫的事件可以用寄送来处理,急迫的就要用发送来处理。如鼠标、键盘消息会被寄送,而按钮等消息则会被发送。>
广播消息用的比较少boardmessage。该函数可以向指定的接收者发送一条消息,这些接收者可以是应用程序、可安装的驱动程序、网络驱动程序、系统级别的设备驱动消息和他们的任意组合。
在多线程应用中,PostMessage的用法还是一样,但SendMessage则不同了。如果在线程A中向线程B所创建的一个窗口hWndB发送消息SendMessage(hWndB,WM_MSG,0,0),那么系统将会立即将执行权从线程A切换到线程B,然后在线程B中调用hWndB的窗口过程来处理消息,并且在处理完该消息后,执行权仍然在B手中!这个时候,线程A就阻塞在SendMessage处,等待下次线程A获得执行权后才继续执行,并且仍然可以获得消息处理的结果(返回值)。
消息的接收主要有3个函数:
GetMessage、PeekMessage、WaitMessage。
GetMessage的主要功能是从消息队列中“取出”消息,消息被取出以后,就从消息队列中将其删除。PeekMessage的主要功能是查看消息,如果有消息,就返回true,否则返回false。也可以使用PeekMessage从消息队列中取出消息。二者不同在于,GetMessage每次都会等待消息,直到取到消息才返回,具有线程同步行为,而PeekMessage不管有没有消息都是要立即返回的,具有线程异步行为。WaitMessage就是如果应用程序中没有消息,就将该应用程序挂起,等有新的消息被放入应用程序中的消息队列里时才会返回。
窗口过程就是一个处理这个窗口的所有消息的函数。每一个窗口类都有一个窗口过程。窗口过程是一个回调函数,每处理一个消息都会返回一个值给windows。
我们的窗口过程是WndProc。
< Windows把消息放到消息队列中,而消息循环将它们发送到相应的窗口过程函数,真正的处理是在窗口过程函数中执行的,在Windows中就使用了回调函数来进行这种通信。>
消息队列:每一个应用程序开始执行,windows都会为该程序创建一个消息队列。
消息循环:用GetMessage不断从消息队列中取出消息,然后再TranslateMessage 解释和DispatchMessage分发消息到窗口过程。
Mfc中所有能够进行消息处理的类都是基于CCmdTarget类。MFC在windows消息机制的基础上,还有一个特殊的消息映射机制,消息与消息处理函数一一对应 的消息映射表。
当窗口接收到消息的时候,会到消息映射表中查找消息的处理函数,然后消息处理函数进行处理。
利用Class Wizard自动添加,右击进入Class Wizard之后选择类的名称,在Message列表中选择消息添加,为控件添加消息等。
如果没有我们想要的消息,就手动添加。定义一个自定义消息,在头文件中声明一个afx_msg开头的函数,在消息映射表中用ON_MESSAGE将函数与消息关联起来。最后再定义该函数。
首先定义一个自定义消息: #define WM_MYMSG WM_USER+1
其次在头文件中声明函数(头文件(.h)中有一个宏DECLARE_MESSAGE_MAP(),它被放在了类的末尾): afx_msg void onMyMsg();
然后在cpp的消息映射表中添加对应关系:
//BEGIN_MESSAGE_MAP(CDefMsgDemoDlg,CDialog)
ON_MESSAGE(WM_MYMSG,onMyMsg)
//END_MESSAGE_MAP()
用ON_MESSAGE将自定义消息号与函数关联在一起。
最后再定义onMyMsg()函数。
#define WM_USER 0x0400
#define WM_APP 0x8000
0到WM_USER-1
Messages reserved for use by the system
系统预留使用的消息
WM_USER到0x7FFF
Integer messages for use by private window classes
被私有窗口类使用的消息
WM_APP到0xBFFF
Messages available for use by applications
被应用程序使用的消息
0xC000到0xFFFF
String messages for use by applications
被应用程序使用的字符串消息
大于0xFFFF
Reserved by the system
系统预留
一般有一个WinMain函数作为程序的入口点,在WinMain里面定义窗口类,进行消息循环。消息循环就是那个普通的while循环,在其中接收消息、分发消息。然后是窗口函数WndProc,名字可以自己定。在其中用一个大的switch结构检索消息,在每个case下面写处理消息的代码。
在MFC中能从代码里看到的入口点是定义一个全局的继承于CWinApp的类.比如CMyApp theApp;这样定义下.在C++中全局变量是先于main被执行的,所以先初始化theApp后才接着调用main。这个main函数是AfxWinMain,是c运行时动态链接库来调用的。
这个AfxWinMain函数中的流程与sdk程序中main函数的流程差不多,细节很不一样。
总结一下就是,,MFC中有main函数,但是由系统去调用.然后main函数里面执行的操作差不多,只不过它是通过CWinApp和CWinThread的指针去调用一些相关的函数,而指针嘛由于调用了虚函数,所以用到了面向对象中的多态。
< Explorer.exe是windows的程序管理器,管理windows图形壳,包括桌面和文件管理,也叫命令解释器,是操作系统引导时就加载的系统进程。>
双击一个可执行程序图标,流程就是:
然后窗口关闭,循环退出,完成一些清理工作,最后是ExitProcess退出进程。
进程之间的通信是指不同进程之间传播和交换消息。
方式有文件映射、共享内存、匿名管道、命名管道、邮件槽、剪切板、socket等。
其中socket和streams支持不同计算机的两个进程。
文件映射:允许多个进程访问同一文件映射对象,各个进程在它自己的地址空间里创建接收内存的指针。通过使用这些指针,不同进程就可以读或修改文件的内容,实现了对文件中数据的共享。
<文件映射只能用于本地机器的进程之间,不能用于网络中,而且要控制好进程同步,参考vs和sourceinsight>
共享内存就是文件映射的一种特殊情况。
匿名管道:匿名管道是在父与子进程之间,或者同属于一个父进程的两个子进程之间传输数据的单向管道。
< 通常由父进程创建管道,然后由要通信的子进程继承通道的读端点句柄或写端点句柄,然后实现通信。这样子进程之间就可以通过管道实现直接通信,不用通过父进程。>
匿名管道不能在网络上使用,也不能用于两个不相关的进程之间。
命名管道:命名管道可以在不相关的进程之间和不同计算机之间进行数据传输的单向或者双向管道。
邮件槽:类似于命名管道,可以在不同进程之间、不同计算机之间进行单向通信,通过邮件槽给邮件槽服务器发送消息,一个进程既可以是邮件槽服务器也可以是邮件槽客户,因此可以建立多个邮件槽来实现进程之间的相互通信。
<与命名管道不同的是,邮件槽是通过不可靠的数据包完成的,如udp包,而命名管道是建立在可靠连接基础上的。>
剪切板:复制粘贴到另一个进程。不能在网络上使用。
管道、消息队列、共享内存、信号量、socket、stream等等。
<只有socket和stream可以用于两个机器之间的进程通信>
管道都是同一个计算机中的进程通信,分为有名管道和无名管道。
无名管道是父进程和子进程之间的通信,有名管道是任意两个进程之间的通信。都是单向通信的。
共享内存:通常由一个进程创建一块共享内存区,其余进程对这块内存区进行读写。
< 共享内存是运行在同一台机器上的进程间通信最快的方式,因为不需要在不同进程中进行复制。>
信号量:信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。是为了保证一个时间只有一个进程访问同一个资源而设置的,也就是为了进程同步而设置的。<信号量为正才允许使用资源>
不是。两个进程指向的同一个地址是虚拟内存地址,然而对应的物理地址实际上是不同的,所以是不同区域。
栈私有、堆公有。
在多线程环境下,每个线程拥有一个栈和一个程序计数器。栈和程序计数器用来保存线程的执行历史和线程的执行状态,是线程私有的资源。其他的资源(比如堆、地址空间、全局变量)是由同一个进程内的多个线程共享。
最大值是200,最小值是2。
对于多线程来说,线程共用一个内存。
i++的操作是每次先把i从内存复制到寄存器,在寄存器中++,然后再把i复制到内存中。
有可能线程a先执行99次++,这时候a寄存器中的值是99,但是并没有写到内存中。然后此时线程b执行一次++,并把1写到内存中,这样a再执行最后一次++,a寄存器中的值就为2,然后等待线程b执行完剩下的99次++,寄存器b中的值写入内存,最后a寄存器中的2再写入内存覆盖掉之前的值,最后就出现2的情况。
最大值是98,最小值是0.
操作系统(OS)是管理和控制计算机硬件与软件资源的一个系统软件,是底层硬件与用户的一个接口,任何其他软件都必须在操作系统的支持下才能运行。OS主要功能是资源管理,程序控制和人机交互。主要由内核、驱动程序、接口库、外围组成。常见的操作系统有Android, iOS, Linux, Windows。
物理内存:是主板上的内存条,大小是固定的。程序运行很多或者程序本身很大的话,就会导致大量的物理内存占用。
虚拟内存:是在硬盘上划分一块页面文件,充当内存。当程序在运行时,有一部分资源还没有用上或者同时打开几个程序却只操作其中一个程序时,系统没必要将程序所有的资源都塞在物理内存中,于是,系统将这些暂时不用的资源放在虚拟内存上,等到需要时在调出来用。
物理地址:就是内存的单元寻址,与处理器和cpu相连接的地址总线相对应。放在寻址总线上的地址。是内存单元的真正地址。
逻辑地址:由程序产生的由段组成的偏移地址部分。比如对一个变量取地址,这个地址指的就是逻辑地址。相对于你当前进程数据段的地址,不和绝对物理地址相干。
虚拟地址:逻辑地址是对应的硬件平台段式管理转换前地址,虚拟地址是对应的硬件平台页式转换前地址。