您的位置:首页 > 职场人生

面试总结4--多线程问题

2015-11-13 16:33 543 查看
多线程问题在面试中经常遇到,比如在面试瑞晟过程中就被重点问到了多线程的知识。

1、以下多线程对int型变量x的操作,哪几个不需要进行同步(D)

A. x=y; B. x++; C. ++x; D. x=1;

2、编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推?
#include "stdio.h"
#include "stdlib.h"
#include <iostream>
#include <string>
#include <stack>
#include <windows.h>
#include <process.h>
#include<iostream>
using namespace std;

const int Numbers = 10;
HANDLE            A,B,C;
int times=1;

unsigned int __stdcall FunA(void *pPM)
{
Sleep(100);//some work should to do
printf("第%d次:\n",times);
times++;
printf("A\n");
ReleaseSemaphore(B, 1, NULL);//递增信号量B的资源数

return 0;
}

unsigned int __stdcall FunB(void *pPM)
{
Sleep(100);
printf("B\n");
ReleaseSemaphore(C, 1, NULL);//递增信号量C的资源数

return 0;
}

unsigned int __stdcall FunC(void *pPM)
{
Sleep(100);
printf("C\n");
ReleaseSemaphore(A, 1, NULL);//递增信号量A的资源数

return 0;
}

int main()
{
//初始化信号量
A = CreateSemaphore(NULL, 1, 1, NULL);//当前1个资源,最大允许1个同时访问
B = CreateSemaphore(NULL, 0, 1, NULL);//当前0个资源,最大允许1个同时访问
C = CreateSemaphore(NULL, 0, 1, NULL);//当前0个资源,最大允许1个同时访问

HANDLE  handle[Numbers];
int i = 0;
while (i < Numbers)
{
WaitForSingleObject(A, INFINITE);  //等待信号量A>0
handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunA, &i, 0, NULL);
WaitForSingleObject(B, INFINITE);  //等待信号量B>0
handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunB, &i, 0, NULL);
WaitForSingleObject(C, INFINITE);  //等待信号量C>0
handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunC, &i, 0, NULL);

++i;
}
WaitForMultipleObjects(Numbers, handle, TRUE, INFINITE);

//销毁信号量
CloseHandle(A);
CloseHandle(B);
CloseHandle(C);
for (i = 0; i < Numbers; i++)
CloseHandle(handle[i]);
return 0;
}
运行结果如下:



3、 线程的基本概念、线程的基本状态及状态之间的关系?
答:线程是进程中的一个执行控制单元,执行路径,一个进程中至少有一个线程在负责控制程序的执行一个进程中如果只有一个执行路径,这个程序称为单线程一个进程中有多个执行路径时,这个程序成为多线程。
线程有四种状态:新生状态、可运行状态、被阻塞状态、死亡状态

2、 线程与进程的区别?
答:a.进程是资源分配的基本单位,线程是cpu调度,或者说是程序执行的最小单位

b.进程有独立的地址空间,系统必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,而运行一个进程中的线程,它们之间共享大部分数据,使用相同的地址空间,当然,线程是拥有自己的局部变量和堆栈(注意不是堆)的。

c.线程之间的通信比较方便。统一进程下的线程共享数据(比如全局变量,静态变量),通过这些数据来通信不仅快捷而且方便,当然如何处理好这些访问的同步与互斥正是编写多线程程序的难点。而进程之间的通信只能通过进程通信的方式进行。

3、多线程有几种实现方法,都是什么?

答:(1)通过操作系统API;
(2)使用标准C++线程支持库;
(3)使用第三方提供的线程库;

4、多线程同步和互斥有几种实现方法,都是什么?
答:线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。

用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。

(1)临界区(CCriticalSection)

当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起,直到拥有临界区的线程放弃临界区为止。具体应用方式:

1、定义临界区对象CcriticalSection g_CriticalSection;

2、在访问共享资源(代码或变量)之前,先获得临界区对象,g_CriticalSection.Lock();

3、访问共享资源后,则放弃临界区对象,g_CriticalSection.Unlock();

(2)事件(CEvent)

事件机制,则允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。比如在某些网络应用程序中,一个线程如A负责侦听通信端口,另外一个线程B负责更新用户数据,利用事件机制,则线程A可以通知线程B何时更新用户数据。每个Cevent对象可以有两种状态:有信号状态和无信号状态。Cevent类对象有两种类型:人工事件和自动事件。

自动事件对象,在被至少一个线程释放后自动返回到无信号状态;

人工事件对象,获得信号后,释放可利用线程,但直到调用成员函数ReSet()才将其设置为无信号状态。在创建Cevent对象时,默认创建的是自动事件。一般通过调用WaitForSingleObject()函数来监视事件状态。

(3)互斥量(CMutex)

互斥对象和临界区对象非常相似,只是其允许在进程间使用,而临界区只限制与同一进程的各个线程之间使用,但是更节省资源,更有效率。

(4)信号量(CSemphore)

当需要一个计数器来限制可以使用某共享资源的线程数目时,可以使用“信号量”对象。CSemaphore类对象保存了对当前访问某一个指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程数目。如果这个计数达到了零,则所有对这个CSemaphore类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零为止。

5、多线程同步和互斥有何异同,什么情况下分别使用他们?举例说明

答:线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

线程互斥是指对于共享的进程系统资源,在各用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使一种特殊的线程同步(下文统称为同步)。

6、在Windows编程中互斥量与临界区比较类似,请分析一下二者的主要区别

答:1)互斥量是内核对象,所以它比临界区更加耗费资源,但是它可以命名,因此可以被其它进程访问

2)从目的是来说,临界区是通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。 互斥量是为协调共同对一个共享资源的单独访问而设计的。

7、进程与线程?

答:进程由两个部分组成:1)操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程的统计信息的地方。2)地址空间。它包含所有可执行模块或DLL模块的代码和数据。它还包含动态内存分配的空间。如线程堆栈和堆分配空间。

线程由两个部分组成:1)线程的内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。2)线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量。当创建线程时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。

8、进程间的通信?

答:进程间通信是管道内存共享消息队列信号量socket

管道分为有名管道和无名管道,无名管道只能用于亲属进程之间的通信,而有名管道则可用于无亲属关系的进程之间。

消息队列是用于两个进程之间的通讯,首先在一个进程中创建一个消息队列,然后再往消息队列中写数据,而另一个进程则从那个消息队列中取数据。需要注意的是,消息队列是用创建文件的方式建立的,如果一个进程向某个消息队列中写入了数据之后,另一个进程并没有取出数据,即使向消息队列中写数据的进程已经结束,保存在消息队列中的数据并没有消失,也就是说下次再从这个消息队列读数据的时候,就是上次的数据!!!!

共享内存通常由一个进程创建,其余进程对这块内存区进行读写。得到共享内存有两种方式:映射/dev/mem设备和内存映像文件。前一种方式不给系统带来额外的开销,但在现实中并不常用,因为它控制存取的是实际的物理内存;

共享内存允许两个或多个进程进程共享同一块内存(这块内存会映射到各个进程自己独立的地址空间)从而使得这些进程可以相互通信。在GNU/Linux中所有的进程都有唯一的虚拟地址空间,而共享内存应用编程接口API允许一个进程使用公共内存区段。但是对内存的共享访问其复杂度也相应增加。共享内存的优点是简易性。使用消息队列时,一个进程要向队列中写入消息,这要引起从用户地址空间向内核地址空间的一次复制,同样一个进程进行消息读取时也要进行一次复制。共享内存的优点是完全省去了这些操作。共享内存会映射到进程的虚拟地址空间,进程对其可以直接访问,避免了数据的复制过程。因此,共享内存是GNU/Linux现在可用的最快速的IPC机制。进程退出时会自动和已经挂接的共享内存区段分离,但是仍建议当进程不再使用共享区段时调用shmdt来卸载区段。注意,当一个进程分支出父进程和子进程时,父进程先前创建的所有共享内存区段都会被子进程继承。如果区段已经做了删除标记(在前面以IPC——RMID指令调用shmctl),而当前挂接数已经变为0,这个区段就会被移除。

9、什么是死锁?其条件是什么?怎样避免死锁?
答:死锁的概念:在两个或多个并发进程中,如果每个进程持有某种资源而又都等待别的进程释放它或它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗地讲,就是两个或多个进程被无限期地阻塞、相互等待的一种状态。
产生死锁的必要条件:
  (1)互斥条件(mutualexclusion),一个资源每次只能被一个进程使用;
  (2)不剥夺条件(nopreemption),进程已获得的资源,在未使用完之前,不能强行剥夺;
  (3)请求和保持条件(hold andwait),一个进程因请求资源而阻塞时,对已获得的资源保持不放;
  (4)环路等待条件(circularwait),若干进程之间形成一种首尾相接的循环等待资源关系。
  这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。
  死锁的解除与预防:理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。所以,在系统设计、进程调度等方面注意如何不让这四个必要条件成立,如何确定资源的合理分配算法,避免进程永久占据系统资源。此外,也要防止进程在处于等待状态的情况下占用资源。因此,对资源的分配要给予合理的规划。
死锁的处理策略:鸵鸟策略、预防策略、避免策略、检测与恢复策略。
1、通过引入事务机制方法是将所有上锁操作均作为事务对待,一旦开始上锁,即确保全部操作均可回退,同时通过锁管理器检测死锁,并剥夺资源(回退事务)
2、 设置死锁超时参数为合理范围,如:3分钟-10分种;超过时间,自动放弃本次操作,避免进程悬挂;
3、 破坏环形等待条件使上锁的顺序必须一致。

10、互斥锁和信号量的区别?
答:1. 互斥量用于线程的互斥,信号量用于线程的同步。

这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。

互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。

同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源

2. 互斥量值只能为0/1,信号量值可以为非负整数。

也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。

3. 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施, 它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: