(网易笔试题)1.请简述使用互斥量(Mutux)和临界区(CriticalSection) 作为同步方法的区别及应用场景
解答:互斥量与关键段的行为完全相同。
- 互斥量是内核对象,而关键段是用户模式下的同步对象。(除非对资源的争夺非常激烈,这种情况下等待关键段饿线程将不得不进入内核模式等待)
- 互斥量比关键段慢(原因如上述)
- 不同进程中的线程可以访问同一个互斥量,这就意味着线程可以在等待对资源的访问权时指定一个最长等待时间。
2.下列哪个不能处理进程间同步问题
A.关键段 B.信号量 C.互斥量 D.事件
解答:在Windows中存在两种是方法可以使线程同步:(1)用户模式下的线程同步:1.关键段 2.读写锁(SRWLock)3.条件变量
(2)用内核对象进行线程同步:1.信号量 2.互斥量 3.事件
内核对象唯一的缺点就是它们的性能,用户模式下的线程同步机制提供了非常好的性能,但是存在一些局限性。
在不同的进程间的线程同步只能使用内核对象。用户模式下的线程同步不能用于不同进程间,所以也不能用于进程间同步。
(网易笔试题)3. Spin Lock是一种较为常见于使用的互斥方法。下面是一种其实现方式
typedef int lock_t
void initlock(volatile lock_t* lock_status)
{
*lock_status=0;
}
void lock(volatile lock_t* lock_status)
{
whils(test_and_set(lock_status)==1);
.
.
}
void unlock(volatile lock_t* lock_status)
{
*lock_status=0;
}
a)volatile关键字的作用
b)怎样优化lock函数(提示:多CPU下如何提高CPU Cache效率)
c) 上述代码可能存在的问题(内存模型考虑)
解答:(参考《Windows核心编程(第五版)》P206)
a) volatile关键字的作用:是告诉编译器不对这个变量lock_t* 做任何优化,使每次取数据都从内存中去。
给一个结构加volatile限定符等于给结构中所有的成员都加volatile限定符,这样可以确保任何一个成员始终都是从内存中读取的。
b)
c)
4.如何让一个循环执行的线程安全退出,请用c++代码实现相应线程函数及退出机制。
解答:让线程自己安全退出有两种方式(1)设置一个全局变量(2)一个事件的形式
(1)设置一个全局变量,当需要退出这个线程时,用另一个线程设置这个全局变量,
代码如下:
#include <iostream>
#include <Windows.h> //CreateThread();HANDLE
#include <process.h> //_beginthreadex()
using namespace std;
int g_iCount=0;
bool g_bThreadFlag=false; //标识是否让线程退出的变量
unsigned int _stdcall ThreadFun(PVOID pM)
{
g_bThreadFlag=true;
while(true)
{
if (!g_bThreadFlag)
{
break;
}
g_iCount++;
cout<<"子线程的ID是:"<<GetCurrentThreadId()<<"是第:"<<g_iCount<<"个"<<endl;
Sleep(1000);
}
return 0;
}
int main()
{
HANDLE handle = (HANDLE)_beginthreadex(NULL,0,ThreadFun,NULL,0,NULL);
Sleep(10000);
if (handle)
{
g_bThreadFlag=false;
WaitForSingleObject(handle,INFINITE);
CloseHandle(handle);
handle=NULL;
}
return 0;
}
(2)一个事件的形式
#include <iostream>
#include <Windows.h> //CreateThread();HANDLE
#include <process.h> //_beginthreadex()
using namespace std;
int g_iCount=0;
HANDLE handleEvent;
unsigned int _stdcall ThreadFun(PVOID pM)
{
while(true)
{
if (WaitForSingleObject(handleEvent,0)!=WAIT_TIMEOUT)
{
//释放资源
cout<<"释放资源"<<endl;
break;
}
g_iCount++;
cout<<"子线程的ID是:"<<GetCurrentThreadId()<<"是第:"<<g_iCount<<"个"<<endl;
Sleep(1000);
}
return 0;
}
int main()
{
handleEvent = CreateEvent(NULL,false,false,NULL);
HANDLE handle = (HANDLE)_beginthreadex(NULL,0,ThreadFun,NULL,0,NULL);
Sleep(2000);
SetEvent(handleEvent);
Sleep(2000);
return 0;
}
补充:
- (1)线程函数返回(最好使用这种方法)
- 这是确保所有线程资源被正确地清除的唯一办法。如果线程能够返回,就可以确保下列事项的实现:在线程函数中创建的所有C++对象均将通过它们的撤消函数正确地撤消。操作系统将正确地释放线程堆栈使用的内存。系统将线程的退出代码设置为线程函数的返回值。系统将递减线程内核对象的使用计数。
- (2)调用ExitThread函数(最好不要使用这种方法)
- 该函数将终止线程的运行,并导致操作系统清除该线程使用的所有操作系统资源。但是,C++资源(如C++类对象)将不被撤消。
- (3)调用TerminateThread函数(应该避免使用这种方法)
- TerminateThread能撤消任何线程。线程的内核对象的使用计数也被递减。TerminateThread函数是异步运行的函数。如果要确切地知道该线程已经终止运行,必须调用WaitForSingleObject或者类似的函数。当使用返回或调用ExitThread的方法撤消线程时,该线程的内存堆栈也被撤消。但是,如果使用TerminateThread,那么在拥有线程的进程终止运行之前,系统不撤消该线程的堆栈。
- (4)包含线程的进程终止运行(应该避免使用这种方法)
- 由于整个进程已经被关闭,进程使用的所有资源肯定已被清除。就像从每个剩余的线程调用TerminateThread一样。这意味着正确的应用程序清除没有发生,即C++对象撤消函数没有被调用,数据没有转至磁盘等等。一旦线程不再运行,系统中就没有别的线程能够处理该线程的句柄。然而别的线程可以调GetExitcodeThread来检查由hThread标识的线程是否已经终止运行。如果它已经终止运行,则确定它的退出代码。
- 安全的结束一个线程的最好的方法就是让线程自己返回。
- 一个比较好的方法如下:
- 1、在主线程中创建一个Event。
- 2、在线程中等待这个Event,如果主线程希望线程终止,释放所有的资源,返回。
-
5.在多线程编程中 为什么尽量使用_beginthreadex()来代替CreateThread();
解答:CreateThread()是在Windows.h,是Windows提供的API接口,_beginthreadex()在process.h是C/C++语言另一个创建线程的函数。_beginThreadex()函数中是调用CreateThread()实现的,并为每个线程都将拥有自己专用的一块内存区域来供标准C运行库中所有有需要的函数使用。这块内存区域的创建就是由 _beginthreadex()来负者的。