精华内容
下载资源
问答
  • C/C++多线程面试题

    万次阅读 2018-05-20 10:56:06
    第一线程的基本概念、线程的基本状态及状态之间的关系? 线程,有时称为轻量级进程,是CPU使用的基本单元;它由线程ID、程序计数器、寄存器集合和堆栈组成。它与属于同一进程的其他线程共...
    转自: https://blog.csdn.net/u010236550/article/details/12372319 
    

    以下提供答案参考:

    第一题:线程的基本概念、线程的基本状态及状态之间的关系?

    线程,有时称为轻量级进程,是CPU使用的基本单元;它由线程ID、程序计数器、寄存器集合和堆栈组成。它与属于同一进程的其他线程共享其代码段、数据段和其他操作系统资源(如打开文件和信号)。

    线程有四种状态:新生状态、可运行状态、被阻塞状态、死亡状态。状态之间的转换如下图所示:

     


    第二题:线程与进程的区别?

    1、 线程是进程的一部分,所以线程有的时候被称为是轻权进程或者轻量级进程。
    2、 一个没有线程的进程是可以被看作单线程的,如果一个进程内拥有多个进程,进程的执行过程不是一条线(线程)的,而是多条线(线程)共同完成的。
    3、 系统在运行的时候会为每个进程分配不同的内存区域,但是不会为线程分配内存(线程所使用的资源是它所属的进程的资源),线程组只能共享资源。那就是说,出了CPU之外(线程在运行的时候要占用CPU资源),计算机内部的软硬件资源的分配与线程无关,线程只能共享它所属进程的资源。
    4、 与进程的控制表PCB相似,线程也有自己的控制表TCB,但是TCB中所保存的线程状态比PCB表中少多了。
    5、 进程是系统所有资源分配时候的一个基本单位,拥有一个完整的虚拟空间地址,并不依赖线程而独立存在。

     

    第三题:多线程有几种实现方法,都是什么?

     1. 继承 Thread 类
      2. 实现 Runnable 接口再 new Thread(YourRunnableOjbect) 

    第四题:多线程同步和互斥有几种实现方法,都是什么?



    线程间的同步方法大体可分为两类:用户模式和内核模式。顾名思义,内核模式就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态,而用户模式就是不需要切换到内核态,只在用户态完成操作。
    用户模式下的方法有:原子操作(例如一个单一的全局变量),临界区。内核模式下的方法有:事件,信号量,互斥量。

     

    第五题:多线程同步和互斥有何异同,在什么情况下分别使用他们?举例说明。

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

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

    1


     

    2、以下多线程对int型变量x的操作,哪几个不需要进行同步(D)
            A. x=y;      B. x++;    C. ++x;    D. x=1;

            详见:后面系列解析

    3、多线程中栈与堆是公有的还是私有的 (C)

            A:栈公有, 堆私有

            B:栈公有,堆公有

            C:栈私有, 堆公有

            D:栈私有,堆私有

    4、临界区(Critical Section)和互斥量(Mutex)

            两者都可以用于同一进程中不同子线程对资源的互斥访问。

            互斥量是内核对象,因此还可以用于不同进程中子线程对资源的互斥访问。

            互斥量可以很好的解决由于线程意外终止资源无法释放的问题。

    5、一个全局变量tally,两个线程并发执行(代码段都是ThreadProc),问两个线程都结束后,tally取值范围。

            inttally = 0;//glable

            voidThreadProc()

            {

                  for(inti = 1;i <= 50;i++)

                       tally += 1;

            }

            答:[50,100]

    6、编写一个程序,开启3个线程,这3个线程的ID分别为A、B、C,每个线程将自己的ID在屏幕上打印10遍,要求输出结果必须按ABC的顺序显示;如:ABCABC….依次递推。

            思路:用信号量进行各个子线程之间的互斥,创建3个信号量A、B、C。初始时A的资源数为1,B、C的资源数为0,访问A之后,将B的资源数加1,访问B之后将C的资源数加1,访问C之后将A的资源数加1。创建3个子线程顺序访问资源A、B、C。

    1. #include “stdafx.h”  
    2. #include “stdio.h”  
    3. #include “stdlib.h”  
    4. #include <iostream>  
    5. #include <string>  
    6. #include <stack>  
    7. #include <windows.h>  
    8. #include <process.h>  
    9. using namespace std;  
    10.   
    11. const int THREAD_NUM = 10;  
    12. HANDLE            ga,gb,gc;  
    13.   
    14. unsigned int __stdcall FunA(void *pPM)  
    15. {  
    16.     Sleep(50);//some work should to do  
    17.     printf(“A\n”);  
    18.     ReleaseSemaphore(gb, 1, NULL);//递增信号量B的资源数    
    19.   
    20.     return 0;  
    21. }  
    22.   
    23. unsigned int __stdcall FunB(void *pPM)  
    24. {     
    25.     Sleep(50);//some work should to do  
    26.     printf(“B\n”);  
    27.     ReleaseSemaphore(gc, 1, NULL);//递增信号量C的资源数    
    28.   
    29.     return 0;  
    30. }  
    31.   
    32. unsigned int __stdcall FunC(void *pPM)  
    33. {  
    34.     Sleep(50);//some work should to do  
    35.     printf(“C\n”);  
    36.     ReleaseSemaphore(ga, 1, NULL);//递增信号量A的资源数    
    37.   
    38.     return 0;  
    39. }  
    40.   
    41. int main()  
    42. {  
    43.     //初始化信号量  
    44.     ga = CreateSemaphore(NULL, 1, 1, NULL);//当前1个资源,最大允许1个同时访问  
    45.     gb = CreateSemaphore(NULL, 0, 1, NULL);//当前0个资源,最大允许1个同时访问  
    46.     gc = CreateSemaphore(NULL, 0, 1, NULL);//当前0个资源,最大允许1个同时访问  
    47.       
    48.     HANDLE  handle[THREAD_NUM];   
    49.     int i = 0;  
    50.     while (i < THREAD_NUM)   
    51.     {  
    52.         WaitForSingleObject(ga, INFINITE);  //等待信号量A>0  
    53.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunA, &i, 0, NULL);  
    54.         WaitForSingleObject(gb, INFINITE);  //等待信号量B>0  
    55.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunB, &i, 0, NULL);  
    56.         WaitForSingleObject(gc, INFINITE);  //等待信号量C>0  
    57.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunC, &i, 0, NULL);  
    58.           
    59.         ++i;  
    60.     }  
    61.     WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);  
    62.       
    63.     //销毁信号量  
    64.     CloseHandle(ga);  
    65.     CloseHandle(gb);  
    66.     CloseHandle(gc);  
    67.     for (i = 0; i < THREAD_NUM; i++)  
    68.         CloseHandle(handle[i]);  
    69.     return 0;  
    70. }  


    1. #include “stdafx.h”  
    2. #include “stdio.h”  
    3. #include “stdlib.h”  
    4. #include <iostream>  
    5. #include <string>  
    6. #include <stack>  
    7. #include <windows.h>  
    8. #include <process.h>  
    9. using namespace std;  
    10.   
    11. const int THREAD_NUM = 10;  
    12. HANDLE            ga,gb,gc;  
    13.   
    14. unsigned int __stdcall FunA(void *pPM)  
    15. {  
    16.     Sleep(50);//some work should to do  
    17.     printf(“A\n”);  
    18.     ReleaseSemaphore(gb, 1, NULL);//递增信号量B的资源数    
    19.   
    20.     return 0;  
    21. }  
    22.   
    23. unsigned int __stdcall FunB(void *pPM)  
    24. {     
    25.     Sleep(50);//some work should to do  
    26.     printf(“B\n”);  
    27.     ReleaseSemaphore(gc, 1, NULL);//递增信号量C的资源数    
    28.   
    29.     return 0;  
    30. }  
    31.   
    32. unsigned int __stdcall FunC(void *pPM)  
    33. {  
    34.     Sleep(50);//some work should to do  
    35.     printf(“C\n”);  
    36.     ReleaseSemaphore(ga, 1, NULL);//递增信号量A的资源数    
    37.   
    38.     return 0;  
    39. }  
    40.   
    41. int main()  
    42. {  
    43.     //初始化信号量  
    44.     ga = CreateSemaphore(NULL, 1, 1, NULL);//当前1个资源,最大允许1个同时访问  
    45.     gb = CreateSemaphore(NULL, 0, 1, NULL);//当前0个资源,最大允许1个同时访问  
    46.     gc = CreateSemaphore(NULL, 0, 1, NULL);//当前0个资源,最大允许1个同时访问  
    47.       
    48.     HANDLE  handle[THREAD_NUM];   
    49.     int i = 0;  
    50.     while (i < THREAD_NUM)   
    51.     {  
    52.         WaitForSingleObject(ga, INFINITE);  //等待信号量A>0  
    53.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunA, &i, 0, NULL);  
    54.         WaitForSingleObject(gb, INFINITE);  //等待信号量B>0  
    55.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunB, &i, 0, NULL);  
    56.         WaitForSingleObject(gc, INFINITE);  //等待信号量C>0  
    57.         handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunC, &i, 0, NULL);  
    58.           
    59.         ++i;  
    60.     }  
    61.     WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);  
    62.       
    63.     //销毁信号量  
    64.     CloseHandle(ga);  
    65.     CloseHandle(gb);  
    66.     CloseHandle(gc);  
    67.     for (i = 0; i < THREAD_NUM; i++)  
    68.         CloseHandle(handle[i]);  
    69.     return 0;  
    70. }  
    #include “stdafx.h”
    #include "stdio.h" #include "stdlib.h" #include <iostream> #include <string> #include <stack> #include <windows.h> #include <process.h> using namespace std; const int THREAD_NUM = 10; HANDLE ga,gb,gc; unsigned int __stdcall FunA(void *pPM) { Sleep(50);//some work should to do printf("A\n"); ReleaseSemaphore(gb, 1, NULL);//递增信号量B的资源数 return 0; } unsigned int __stdcall FunB(void *pPM) { Sleep(50);//some work should to do printf("B\n"); ReleaseSemaphore(gc, 1, NULL);//递增信号量C的资源数 return 0; } unsigned int __stdcall FunC(void *pPM) { Sleep(50);//some work should to do printf("C\n"); ReleaseSemaphore(ga, 1, NULL);//递增信号量A的资源数 return 0; } int main() { //初始化信号量 ga = CreateSemaphore(NULL, 1, 1, NULL);//当前1个资源,最大允许1个同时访问 gb = CreateSemaphore(NULL, 0, 1, NULL);//当前0个资源,最大允许1个同时访问 gc = CreateSemaphore(NULL, 0, 1, NULL);//当前0个资源,最大允许1个同时访问 HANDLE handle[THREAD_NUM]; int i = 0; while (i < THREAD_NUM) { WaitForSingleObject(ga, INFINITE); //等待信号量A>0 handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunA, &i, 0, NULL); WaitForSingleObject(gb, INFINITE); //等待信号量B>0 handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunB, &i, 0, NULL); WaitForSingleObject(gc, INFINITE); //等待信号量C>0 handle[i] = (HANDLE)_beginthreadex(NULL, 0, FunC, &i, 0, NULL); ++i; } WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE); //销毁信号量 CloseHandle(ga); CloseHandle(gb); CloseHandle(gc); for (i = 0; i < THREAD_NUM; i++) CloseHandle(handle[i]); return 0; }

    7、生产者消费者问题:有一个生产者在生产产品,这些产品将提供给若干个消费者去消费,为了使生产者和消费者能并发执行,在两者之间设置一个有多个缓冲区的缓冲池,生产者将它生产的产品放入一个缓冲区中,消费者可以从缓冲区中取走产品进行消费,所有生产者和消费者都是异步方式运行的,但它们必须保持同步,即不允许消费者到一个空的缓冲区中取产品,也不允许生产者向一个已经装满产品且尚未被取走的缓冲区中投放产品。

            分析:假设1个生产者,2个消费者,缓冲区大小为4。

    第一.从缓冲区取出产品和向缓冲区投放产品必须是互斥进行的。可以用关键段和互斥量来完成。

    第二.生产者要等待缓冲区为空,这样才可以投放产品,消费者要等待缓冲区不为空,这样才可以取出产品进行消费。并且由于有二个等待过程,所以要用二个事件或信号量来控制。

    代码如下:

    1. //1生产者 2消费者 4缓冲区  
    2. #include “stdafx.h”  
    3. #include “stdio.h”  
    4. #include “stdlib.h”  
    5. #include <iostream>  
    6. #include <string>  
    7. #include <stack>  
    8. #include <windows.h>  
    9. #include <process.h>  
    10. using namespace std;  
    11.   
    12. //设置控制台输出颜色  
    13. BOOL SetConsoleColor(WORD wAttributes)  
    14. {  
    15.     HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);  
    16.     if (hConsole == INVALID_HANDLE_VALUE)  
    17.         return FALSE;  
    18.       
    19.     return SetConsoleTextAttribute(hConsole, wAttributes);  
    20. }  
    21.   
    22. const int END_PRODUCE_NUMBER = 8;   //生产产品个数  
    23. const int BUFFER_SIZE = 4;          //缓冲区个数  
    24. int g_Buffer[BUFFER_SIZE];          //缓冲池  
    25. int g_i, g_j;  
    26. CRITICAL_SECTION g_cs;              //信号量与关键段  
    27. HANDLE g_hSemaphoreBufferEmpty, g_hSemaphoreBufferFull;  
    28.   
    29. //生产者线程函数  
    30. unsigned int __stdcall ProducerThreadFun(PVOID pM)  
    31. {  
    32.     for (int i = 1; i <= END_PRODUCE_NUMBER; i++)  
    33.     {  
    34.         //等待有空的缓冲区出现  
    35.         WaitForSingleObject(g_hSemaphoreBufferEmpty, INFINITE);  
    36.   
    37.         //互斥的访问缓冲区  
    38.         EnterCriticalSection(&g_cs);  
    39.         g_Buffer[g_i] = i;  
    40.         printf(“生产者在缓冲池第%d个缓冲区中投放数据%d\n”, g_i, g_Buffer[g_i]);  
    41.         g_i = (g_i + 1) % BUFFER_SIZE;  
    42.         LeaveCriticalSection(&g_cs);  
    43.   
    44.         //通知消费者有新数据了  
    45.         ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL);  
    46.     }  
    47.     printf(“生产者完成任务,线程结束运行\n”);  
    48.     return 0;  
    49. }  
    50.   
    51. //消费者线程函数  
    52. unsigned int __stdcall ConsumerThreadFun(PVOID pM)  
    53. {  
    54.     while (true)  
    55.     {  
    56.         //等待非空的缓冲区出现  
    57.         WaitForSingleObject(g_hSemaphoreBufferFull, INFINITE);  
    58.           
    59.         //互斥的访问缓冲区  
    60.         EnterCriticalSection(&g_cs);  
    61.         SetConsoleColor(FOREGROUND_GREEN);  
    62.         printf(“  编号为%d的消费者从缓冲池中第%d个缓冲区取出数据%d\n”, GetCurrentThreadId(), g_j, g_Buffer[g_j]);  
    63.         SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);  
    64.         if (g_Buffer[g_j] == END_PRODUCE_NUMBER)//结束标志  
    65.         {  
    66.             LeaveCriticalSection(&g_cs);  
    67.             //通知其它消费者有新数据了(结束标志)  
    68.             ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL);  
    69.             break;  
    70.         }  
    71.         g_j = (g_j + 1) % BUFFER_SIZE;  
    72.         LeaveCriticalSection(&g_cs);  
    73.   
    74.         Sleep(50); //some other work to do  
    75.   
    76.         ReleaseSemaphore(g_hSemaphoreBufferEmpty, 1, NULL);  
    77.     }  
    78.     SetConsoleColor(FOREGROUND_GREEN);  
    79.     printf(“  编号为%d的消费者收到通知,线程结束运行\n”, GetCurrentThreadId());  
    80.     SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);  
    81.     return 0;  
    82. }  
    83.   
    84. int main()  
    85. {  
    86.     InitializeCriticalSection(&g_cs);  
    87.     //初始化信号量,一个记录有产品的缓冲区个数,另一个记录空缓冲区个数.  
    88.     g_hSemaphoreBufferEmpty = CreateSemaphore(NULL, 4, 4, NULL);  
    89.     g_hSemaphoreBufferFull  = CreateSemaphore(NULL, 0, 4, NULL);  
    90.     g_i = 0;  
    91.     g_j = 0;  
    92.     memset(g_Buffer, 0, sizeof(g_Buffer));  
    93.   
    94.     const int THREADNUM = 3;  
    95.     HANDLE hThread[THREADNUM];  
    96.     //生产者线程  
    97.     hThread[0] = (HANDLE)_beginthreadex(NULL, 0, ProducerThreadFun, NULL, 0, NULL);  
    98.     //消费者线程  
    99.     hThread[1] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL);  
    100.     hThread[2] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL);  
    101.     WaitForMultipleObjects(THREADNUM, hThread, TRUE, INFINITE);  
    102.   
    103.     for (int i = 0; i < THREADNUM; i++)        
    104.         CloseHandle(hThread[i]);  
    105.   
    106.     //销毁信号量和关键段  
    107.     CloseHandle(g_hSemaphoreBufferEmpty);  
    108.     CloseHandle(g_hSemaphoreBufferFull);  
    109.     DeleteCriticalSection(&g_cs);  
    110.     return 0;  
    111. }  

    1. //1生产者 2消费者 4缓冲区  
    2. #include “stdafx.h”  
    3. #include “stdio.h”  
    4. #include “stdlib.h”  
    5. #include <iostream>  
    6. #include <string>  
    7. #include <stack>  
    8. #include <windows.h>  
    9. #include <process.h>  
    10. using namespace std;  
    11.   
    12. //设置控制台输出颜色  
    13. BOOL SetConsoleColor(WORD wAttributes)  
    14. {  
    15.     HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);  
    16.     if (hConsole == INVALID_HANDLE_VALUE)  
    17.         return FALSE;  
    18.       
    19.     return SetConsoleTextAttribute(hConsole, wAttributes);  
    20. }  
    21.   
    22. const int END_PRODUCE_NUMBER = 8;   //生产产品个数  
    23. const int BUFFER_SIZE = 4;          //缓冲区个数  
    24. int g_Buffer[BUFFER_SIZE];          //缓冲池  
    25. int g_i, g_j;  
    26. CRITICAL_SECTION g_cs;              //信号量与关键段  
    27. HANDLE g_hSemaphoreBufferEmpty, g_hSemaphoreBufferFull;  
    28.   
    29. //生产者线程函数  
    30. unsigned int __stdcall ProducerThreadFun(PVOID pM)  
    31. {  
    32.     for (int i = 1; i <= END_PRODUCE_NUMBER; i++)  
    33.     {  
    34.         //等待有空的缓冲区出现  
    35.         WaitForSingleObject(g_hSemaphoreBufferEmpty, INFINITE);  
    36.   
    37.         //互斥的访问缓冲区  
    38.         EnterCriticalSection(&g_cs);  
    39.         g_Buffer[g_i] = i;  
    40.         printf(“生产者在缓冲池第%d个缓冲区中投放数据%d\n”, g_i, g_Buffer[g_i]);  
    41.         g_i = (g_i + 1) % BUFFER_SIZE;  
    42.         LeaveCriticalSection(&g_cs);  
    43.   
    44.         //通知消费者有新数据了  
    45.         ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL);  
    46.     }  
    47.     printf(“生产者完成任务,线程结束运行\n”);  
    48.     return 0;  
    49. }  
    50.   
    51. //消费者线程函数  
    52. unsigned int __stdcall ConsumerThreadFun(PVOID pM)  
    53. {  
    54.     while (true)  
    55.     {  
    56.         //等待非空的缓冲区出现  
    57.         WaitForSingleObject(g_hSemaphoreBufferFull, INFINITE);  
    58.           
    59.         //互斥的访问缓冲区  
    60.         EnterCriticalSection(&g_cs);  
    61.         SetConsoleColor(FOREGROUND_GREEN);  
    62.         printf(“  编号为%d的消费者从缓冲池中第%d个缓冲区取出数据%d\n”, GetCurrentThreadId(), g_j, g_Buffer[g_j]);  
    63.         SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);  
    64.         if (g_Buffer[g_j] == END_PRODUCE_NUMBER)//结束标志  
    65.         {  
    66.             LeaveCriticalSection(&g_cs);  
    67.             //通知其它消费者有新数据了(结束标志)  
    68.             ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL);  
    69.             break;  
    70.         }  
    71.         g_j = (g_j + 1) % BUFFER_SIZE;  
    72.         LeaveCriticalSection(&g_cs);  
    73.   
    74.         Sleep(50); //some other work to do  
    75.   
    76.         ReleaseSemaphore(g_hSemaphoreBufferEmpty, 1, NULL);  
    77.     }  
    78.     SetConsoleColor(FOREGROUND_GREEN);  
    79.     printf(“  编号为%d的消费者收到通知,线程结束运行\n”, GetCurrentThreadId());  
    80.     SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);  
    81.     return 0;  
    82. }  
    83.   
    84. int main()  
    85. {  
    86.     InitializeCriticalSection(&g_cs);  
    87.     //初始化信号量,一个记录有产品的缓冲区个数,另一个记录空缓冲区个数.  
    88.     g_hSemaphoreBufferEmpty = CreateSemaphore(NULL, 4, 4, NULL);  
    89.     g_hSemaphoreBufferFull  = CreateSemaphore(NULL, 0, 4, NULL);  
    90.     g_i = 0;  
    91.     g_j = 0;  
    92.     memset(g_Buffer, 0, sizeof(g_Buffer));  
    93.   
    94.     const int THREADNUM = 3;  
    95.     HANDLE hThread[THREADNUM];  
    96.     //生产者线程  
    97.     hThread[0] = (HANDLE)_beginthreadex(NULL, 0, ProducerThreadFun, NULL, 0, NULL);  
    98.     //消费者线程  
    99.     hThread[1] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL);  
    100.     hThread[2] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL);  
    101.     WaitForMultipleObjects(THREADNUM, hThread, TRUE, INFINITE);  
    102.   
    103.     for (int i = 0; i < THREADNUM; i++)        
    104.         CloseHandle(hThread[i]);  
    105.   
    106.     //销毁信号量和关键段  
    107.     CloseHandle(g_hSemaphoreBufferEmpty);  
    108.     CloseHandle(g_hSemaphoreBufferFull);  
    109.     DeleteCriticalSection(&g_cs);  
    110.     return 0;  
    111. }  
    //1生产者 2消费者 4缓冲区
    
    
    
    
    
    #include "stdafx.h" #include "stdio.h" #include "stdlib.h" #include <iostream> #include <string> #include <stack> #include <windows.h> #include <process.h> using namespace std; //设置控制台输出颜色 BOOL SetConsoleColor(WORD wAttributes) { HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); if (hConsole == INVALID_HANDLE_VALUE) return FALSE; return SetConsoleTextAttribute(hConsole, wAttributes); } const int END_PRODUCE_NUMBER = 8; //生产产品个数 const int BUFFER_SIZE = 4; //缓冲区个数 int g_Buffer[BUFFER_SIZE]; //缓冲池 int g_i, g_j; CRITICAL_SECTION g_cs; //信号量与关键段 HANDLE g_hSemaphoreBufferEmpty, g_hSemaphoreBufferFull; //生产者线程函数 unsigned int __stdcall ProducerThreadFun(PVOID pM) { for (int i = 1; i <= END_PRODUCE_NUMBER; i++) { //等待有空的缓冲区出现 WaitForSingleObject(g_hSemaphoreBufferEmpty, INFINITE); //互斥的访问缓冲区 EnterCriticalSection(&g_cs); g_Buffer[g_i] = i; printf(“生产者在缓冲池第%d个缓冲区中投放数据%d\n”, g_i, g_Buffer[g_i]); g_i = (g_i + 1) % BUFFER_SIZE; LeaveCriticalSection(&g_cs); //通知消费者有新数据了 ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL); } printf(“生产者完成任务,线程结束运行\n”); return 0; } //消费者线程函数 unsigned int __stdcall ConsumerThreadFun(PVOID pM) { while (true) { //等待非空的缓冲区出现 WaitForSingleObject(g_hSemaphoreBufferFull, INFINITE); //互斥的访问缓冲区 EnterCriticalSection(&g_cs); SetConsoleColor(FOREGROUND_GREEN); printf(” 编号为%d的消费者从缓冲池中第%d个缓冲区取出数据%d\n”, GetCurrentThreadId(), g_j, g_Buffer[g_j]); SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); if (g_Buffer[g_j] == END_PRODUCE_NUMBER)//结束标志 { LeaveCriticalSection(&g_cs); //通知其它消费者有新数据了(结束标志) ReleaseSemaphore(g_hSemaphoreBufferFull, 1, NULL); break; } g_j = (g_j + 1) % BUFFER_SIZE; LeaveCriticalSection(&g_cs); Sleep(50); //some other work to do ReleaseSemaphore(g_hSemaphoreBufferEmpty, 1, NULL); } SetConsoleColor(FOREGROUND_GREEN); printf(” 编号为%d的消费者收到通知,线程结束运行\n”, GetCurrentThreadId()); SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE); return 0; } int main() { InitializeCriticalSection(&g_cs); //初始化信号量,一个记录有产品的缓冲区个数,另一个记录空缓冲区个数. g_hSemaphoreBufferEmpty = CreateSemaphore(NULL, 4, 4, NULL); g_hSemaphoreBufferFull = CreateSemaphore(NULL, 0, 4, NULL); g_i = 0; g_j = 0; memset(g_Buffer, 0, sizeof(g_Buffer)); const int THREADNUM = 3; HANDLE hThread[THREADNUM]; //生产者线程 hThread[0] = (HANDLE)_beginthreadex(NULL, 0, ProducerThreadFun, NULL, 0, NULL); //消费者线程 hThread[1] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL); hThread[2] = (HANDLE)_beginthreadex(NULL, 0, ConsumerThreadFun, NULL, 0, NULL); WaitForMultipleObjects(THREADNUM, hThread, TRUE, INFINITE); for (int i = 0; i < THREADNUM; i++) CloseHandle(hThread[i]); //销毁信号量和关键段 CloseHandle(g_hSemaphoreBufferEmpty); CloseHandle(g_hSemaphoreBufferFull); DeleteCriticalSection(&g_cs); return 0; }

    8、读者写者问题:这也是一个非常经典的多线程题目,题目大意如下:有一个写者很多读者,多个读者可以同时读文件,但写者在写文件时不允许有读者在读文件,同样有读者读时写者也不能写。

        分析:首先来找找哪些是属于“等待”情况。

    第一、写者要等到没有读者时才能去写文件。

    第二、所有读者要等待写者完成写文件后才能去读文件。

    找完“等待”情况后,再看看有没有要互斥访问的资源。由于只有一个写者而读者们是可以共享的读文件,所以按题目要求并没有需要互斥访问的资源。代码如下:

    1. #include “stdafx.h”  
    2. #include “stdio.h”  
    3. #include “stdlib.h”  
    4. #include <iostream>  
    5. #include <string>  
    6. #include <stack>  
    7. #include <windows.h>  
    8. #include <process.h>  
    9. using namespace std;  
    10.   
    11. //读者与写者问题  
    12. #include <stdio.h>  
    13. #include <process.h>  
    14. #include <windows.h>  
    15. //设置控制台输出颜色  
    16. BOOL SetConsoleColor(WORD wAttributes)  
    17. {  
    18.     HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);  
    19.     if (hConsole == INVALID_HANDLE_VALUE)  
    20.         return FALSE;  
    21.       
    22.     return SetConsoleTextAttribute(hConsole, wAttributes);  
    23. }  
    24. const int READER_NUM = 5;  //读者个数  
    25. //关键段和事件  
    26. CRITICAL_SECTION g_cs, g_cs_writer_count;  
    27. HANDLE g_hEventWriter, g_hEventNoReader;  
    28. int g_nReaderCount;  
    29. //读者线程输出函数(变参函数的实现)  
    30. void ReaderPrintf(char *pszFormat, …)  
    31. {  
    32.     va_list   pArgList;  
    33.       
    34.     va_start(pArgList, pszFormat);  
    35.     EnterCriticalSection(&g_cs);  
    36.     vfprintf(stdout, pszFormat, pArgList);  
    37.     LeaveCriticalSection(&g_cs);  
    38.     va_end(pArgList);  
    39. }  
    40. //读者线程函数  
    41. unsigned int __stdcall ReaderThreadFun(PVOID pM)  
    42. {  
    43.     ReaderPrintf(“     编号为%d的读者进入等待中…\n”, GetCurrentThreadId());  
    44.     //等待写者完成  
    45.     WaitForSingleObject(g_hEventWriter, INFINITE);  
    46.   
    47.     //读者个数增加  
    48.     EnterCriticalSection(&g_cs_writer_count);  
    49.     g_nReaderCount++;  
    50.     if (g_nReaderCount == 1)  
    51.         ResetEvent(g_hEventNoReader);  
    52.     LeaveCriticalSection(&g_cs_writer_count);  
    53.   
    54.     //读取文件  
    55.     ReaderPrintf(“编号为%d的读者开始读取文件…\n”, GetCurrentThreadId());  
    56.   
    57.     Sleep(rand() % 100);  
    58.   
    59.     //结束阅读,读者个数减小,空位增加  
    60.     ReaderPrintf(“ 编号为%d的读者结束读取文件\n”, GetCurrentThreadId());  
    61.   
    62.     //读者个数减少  
    63.     EnterCriticalSection(&g_cs_writer_count);  
    64.     g_nReaderCount–;  
    65.     if (g_nReaderCount == 0)  
    66.         SetEvent(g_hEventNoReader);  
    67.     LeaveCriticalSection(&g_cs_writer_count);  
    68.   
    69.     return 0;  
    70. }  
    71. //写者线程输出函数  
    72. void WriterPrintf(char *pszStr)  
    73. {  
    74.     EnterCriticalSection(&g_cs);  
    75.     SetConsoleColor(FOREGROUND_GREEN);  
    76.     printf(“     %s\n”, pszStr);  
    77.     SetConsoleColor(FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE);  
    78.     LeaveCriticalSection(&g_cs);  
    79. }  
    80. //写者线程函数  
    81. unsigned int __stdcall WriterThreadFun(PVOID pM)  
    82. {  
    83.     WriterPrintf(“写者线程进入等待中…”);  
    84.     //等待读文件的读者为零  
    85.     WaitForSingleObject(g_hEventNoReader, INFINITE);  
    86.     //标记写者正在写文件  
    87.     ResetEvent(g_hEventWriter);  
    88.           
    89.     //写文件  
    90.     WriterPrintf(“  写者开始写文件…..”);  
    91.     Sleep(rand() % 100);  
    92.     WriterPrintf(“  写者结束写文件”);  
    93.   
    94.     //标记写者结束写文件  
    95.     SetEvent(g_hEventWriter);  
    96.     return 0;  
    97. }  
    98.   
    99. int main()  
    100. {  
    101.     printf(“  读者写者问题\n”);  
    102.     printf(“ – by MoreWindows( http://blog.csdn.net/MoreWindows ) –\n\n”);  
    103.   
    104.     //初始化事件和信号量  
    105.     InitializeCriticalSection(&g_cs);  
    106.     InitializeCriticalSection(&g_cs_writer_count);  
    107.   
    108.     //手动置位,初始已触发  
    109.     g_hEventWriter = CreateEvent(NULL, TRUE, TRUE, NULL);  
    110.     g_hEventNoReader  = CreateEvent(NULL, FALSE, TRUE, NULL);  
    111.     g_nReaderCount = 0;  
    112.   
    113.     int i;  
    114.     HANDLE hThread[READER_NUM + 1];  
    115.     //先启动二个读者线程  
    116.     for (i = 1; i <= 2; i++)  
    117.         hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL);  
    118.     //启动写者线程  
    119.     hThread[0] = (HANDLE)_beginthreadex(NULL, 0, WriterThreadFun, NULL, 0, NULL);  
    120.     Sleep(50);  
    121.     //最后启动其它读者结程  
    122.     for ( ; i <= READER_NUM; i++)  
    123.         hThread[i] = (HANDLE)_beginthreadex(NULL, 0, ReaderThreadFun, NULL, 0, NULL);  
    124.     WaitForMultipleObjects(READER_NUM + 1, hThread, TRUE, INFINITE);  
    125.     for (i = 0; i < READER_NUM + 1; i++)  
    126.         CloseHandle(hThread[i]);  
    127.   
    128.     //销毁事件和信号量  
    129.     CloseHandle(g_hEventWriter);  
    130.     CloseHandle(g_hEventNoReader);  
    131.     DeleteCriticalSection(&g_cs);  
    132.     DeleteCriticalSection(&g_cs_writer_count);  
    133.     return 0;  
    134. }  

     

    展开全文
  • C++ 多线程 面试题详解

    万次阅读 2018-01-16 22:33:46
    首先看看堆栈 堆: 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。...栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 t...
     
    首先看看堆栈
    堆: 是大家共有的空间,分全局堆和局部堆。全局堆就是所有没有分配的空间,局部堆就是用户分配的空间。堆在操作系统对进程初始化的时候分配,运行过程中也可以向系统要额外的堆,但是记得用完了要还给操作系统,要不然就是内存泄漏。
    栈:是个线程独有的,保存其运行状态和局部自动变量的。栈在线程开始的时候初始化,每个线程的栈互相独立,因此,栈是 thread safe的。每个C ++对象的数据成员也存在在栈中,每个函数都有自己的栈,栈被用来在函数之间传递参数。操作系统在切换线程的时候会自动的切换栈,就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放。

     

    然后看一下Linux的内存空间模型,这个图在很多地方都看过。
     
    从上图可提取以下三点信息:
    1. 进程栈空间从0xC0000000往下进行分配
    2. 运行时堆通过malloc进行分配,位置处于.bss与0x40000000之间
    3. 在Glibc的malloc中,对于小于128k的需求,直接从堆中分配,而大于128k的需求则通过mmap从文件映射区之后分配,因此地址会大于0x40000000.

    因此,对于以LinuxThread实现的多线程而言,要区分以下两种情况:

    1. 管理线程, 管理线程的堆栈与普通进程无异,即以上提到的三点 (这里我认为,就是指主线程)
    2. 普通线程,因为线程会 在进程堆中申请一块空间(THREAD_MANAGER_STACK_SIZE=8M?) 当作自已的运行栈,而我们知道,大于128k的malloc会采用mmap方式从文件映射区之后分配,所以,普通 线程的栈地址就在这个空间内,即大于0x40000000

     

     

    另外,msdn forum上面有个回答。

    When we create a new thread, a new stack is allocated by CLR, with 1MB memory space, the stack is used to store method local variable, or parameters passed to next method.// CLR分配线程栈

     

    A stack is the snapshot of a thread's execution, from the stack, we can know what a thread is doing, which method the thread is now executing; Generally, different threads have different Method Call Sequence, so their stacks store different data.//不同的线程有不同的线程栈

     

    If we use synchronization logic in our code (for example, use lock keyword), supposing thread A has entered the critical area, thread A's stack will keep on growing (because of calling other methods or allocating more local variables); when thread B trying to enter same area, a data structure in the Heap will be checked, the data structure can tell that the critical area is occupied by thread A, so thread B will wait there.//堆里面的一个数据结构会检查,告诉临界区已经属于A,所以B会等待。

     

    Threads are scheduled by OS (according to the priority), so thread A can create/start thread B, but thread A cannot assign CPU time to thread B. from the perspective of OS, all threads are same with each other, no primary, no secondary.//线程可以创建线程,但是不能分配cpu时间,因为这是由操作系统干的

     

    1.进程和线程有什么区别?

    这个一个最常见,却最不好回答的问题,csdn上面一位博主给出的解答和另一位cnblog博主的解答稍微清晰些一些,总结起来,就是一下的几个区别:

        a.进程是资源分配的基本单位,线程是cpu调度,或者说是程序执行的最小单位。在Mac、Windows NT等采用微内核结构的操作系统中,进程的功能发生了变化:它只是资源分配的单位,而不再是调度运行的单位。在微内核系统中,真正调度运行的基本单位是线程。因此,实现并发功能的单位是线程。

        b.进程有独立的地址空间,比如在linux下面启动一个新的进程,系统必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种非常昂贵的多任务工作方式。而运行一个进程中的线程,它们之间共享大部分数据,使用相同的地址空间,因此启动一个线程,切换一个线程远比进程操作要快,花费也要小得多。当然,线程是拥有自己的局部变量和堆栈(注意不是堆)的,比如在windows中用_beginthreadex创建一个新进程就会在调用CreateThread的同时申请一个专属于线程的数据块(_tiddata)。

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

        d.由b,可以轻易地得到结论:多进程比多线程程序要健壮。一个线程死掉整个进程就死掉了,但是在保护模式下,一个进程死掉对另一个进程没有直接影响。

        e.线程的执行与进程是有区别的。每个独立的线程有有自己的一个程序入口,顺序执行序列和程序的出口,但是线程不能独立执行,必须依附与程序之中,由应用程序提供多个线程的并发控制。

    2. 什么是线程安全?(2012年5月百度实习生面试)

      如果多线程的程序运行结果是可预期的,而且与单线程的程序运行结果一样,那么说明是“线程安全”的。

    3. 秒杀多线程中的题目 解答

      a.线程的概念、线程的基本状态及状态之间的关系

        另外,这个网址里面讲操作系统的知识倒是挺详实的,还有另外一种解释线程概念

      b.多线程的几种实现方法分别是什么?

        这个貌似在java面试中会出现,我是专注于c++的,无视掉,但是不得不说,秒杀多线程面试题系列真心是个好总结

      c.多线程同步与互斥有几种实现方法?都是什么?(C++)

       临界区(CS:critical section)、事件(Event)、互斥量(Mutex)、信号量(semaphores),需要注意的是,临界区是效率最高的,因为基本不需要其他的开销,二内核对象涉及到用户态和内核态的切换,开销较大,另外,关键段、互斥量具有线程所有权的概念,因此只可以用于线程之间互斥,而不能用到同步中。只有互斥量能完美解决进程意外终止所造成的“遗弃问题”。

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

       所谓同步,表示有先有后,比较正式的解释是“线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。”所谓互斥,比较正式的说明是“线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。”表示不能同时访问,也是个顺序问题,所以互斥是一种特殊的同步操作。

       举个例子,设有一个全局变量global,为了保证线程安全,我们规定只有当主线程修改了global之后下一个子线程才能访问global,这就需要同步主线程与子线程,可用关键段实现。当一个子线程访问global的时候另一个线程不能访问global,那么就需要互斥。

       e.以下多线程对int型变量x的操作,哪几个需要进行同步: 
        A. x=y;      B. x++;    C. ++x;    D. x=1;

               答案是ABC,显然,y的写入与x读y要同步,x++和++x都要知道x之前的值,所以也要同步。

       f.多线程中栈与堆是公有的还是私有的

        A:栈公有, 堆私有

        B:栈公有,堆公有

        C:栈私有, 堆公有

        D:栈私有,堆私有

        答案是C,栈一般存放局部变量,而程序员一般自己申请和释放堆中的数据(详见堆与栈的区别)。

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

         临界区、互斥量、信号灯、事件的区别总结

         针对这个题目的话,答案主要有以下几点:

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

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

       h.一个全局变量tally,两个线程并发执行(代码段都是ThreadProc),问两个线程都结束后,tally取值范围。 

     
        inttally = 0; //global
      
        voidThreadProc()
      
        {
      
                 for (inti = 1; i <= 50; i++)
      
                     tally += 1;
      
        }

     

        当两线程串行时,结果最大为100,当某个线程运行结束,而此时另外一个线程刚取出0,还未计算时,结果最小为50。

    【参考资料】
    展开全文
  • 该文档是我总结的c++笔试面试通常会问的一些问题。另外可以从我的资源目录下找c++数据结构与算法,c++笔试面试基础两部分材料,综合这三个材料一起看,我相信大家一定尽快找到工作。
  • Google C++多线程面试题

    2019-03-07 14:56:15
    Google C++多线程面试题 有四个线程1、2、3、4。线程1的功能就是输出1,线程2的功能就是输出2,以此类推………现在有四个文件ABCD。初始都为空。现要让四个文件呈如下格式: A:1 2 3 4 1 2…. B:2 3 4 1 2 3…....

    Google C++多线程面试题

    有四个线程1、2、3、4。线程1的功能就是输出1,线程2的功能就是输出2,以此类推………现在有四个文件ABCD。初始都为空。现要让四个文件呈如下格式:

    A:1 2 3 4 1 2….

    B:2 3 4 1 2 3….

    C:3 4 1 2 3 4….

    D:4 1 2 3 4 1….

    请设计程序。

    网上居然少有合适答案,有个别还行,但是很繁琐(面试时间有限,不会只有一题)。下面是我的答案。

    #include <thread>
    #include <mutex>
    #include <iostream>
    #include <sstream>
    #include <string>

    using namespace std;

    struct SBufferInfo
    {
        string  _buffer;
        mutex   _m;
        int     _prevID;
    }buffers[4];

    void worker(int t)
    {
        for (int j = 0; j < 10; j++)
        {
            for (int i = 0; i < 4;)
            {
                lock_guard<mutex> lk(buffers[i]._m);

                if (t == (buffers[i]._prevID + 1) % 4)
                {
                    buffers[i]._buffer.append(1, '1' + t);
                    buffers[i]._buffer.append(1, ' ');

                    buffers[i]._prevID = t;
                    i++;
                }
                else
                {
                    continue;
                }
            }
        }
    }

    int main()
    {
        for (int i = 0; i < 4; ++i)
            buffers[i]._prevID = (3 + i) % 4;

        std::thread t1(worker, 0);
        std::thread t2(worker, 1);
        std::thread t3(worker, 2);
        std::thread t4(worker, 3);

        t1.join();
        t2.join();
        t3.join();
        t4.join();

        for (int i = 0; i < 4; ++i)
            cout << buffers[i]._buffer << endl;

    }

    展开全文
  • 面试题c++mysql多线程操作系统 面试题c++mysql多线程操作系统 面试题c++mysql多线程操作系统 面试题c++mysql多线程操作系统 面试题c++mysql多线程操作系统
  • 本教程旨在提取最精炼、实用的C++面试知识点,供读者快速学习及本人查阅复习所用。 第一章 C++基本语法 C++ 程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。 对象 -对象具有状态和行为。例如:...

    参考:https://www.runoob.com/cplusplus/cpp-tutorial.html

    本教程旨在提取最精炼、实用的C++面试知识点,供读者快速学习及本人查阅复习所用。

    目录

    第九章  多线程

    9.1  基本概念

    9.2  C++线程管理

    9.3  线程的同步与互斥

    9.4  C++中的几种锁

    9.5  C++中的原子操作

    9.6  相关面试题


    第九章  多线程

    多线程是多任务处理的一种特殊形式,一般情况下,有基于进程和基于线程的两种类型的多任务处理方式。

    • 基于进程的多任务处理是程序的并发执行。
    • 基于线程的多任务处理是同一程序的片段的并发执行。

    9.1  基本概念

    9.1.1  进程与线程

    进程是资源分配和调度的一个独立单位;而线程是进程的一个实体,是CPU调度和分配的基本单位。

    同一个进程中的多个线程的内存资源是共享的,各线程都可以改变进程中的变量。因此在执行多线程运算的时候要注意执行顺序。

    9.1.2  并行与并发

    并行(parallellism)指的是多个任务在同一时刻同时在执行。

    并发(concurrency)是指在一个时间段内,多个任务交替进行。虽然看起来像在同时执行,但其实是交替的。

    9.2  C++线程管理

    • C++11的标准库中提供了多线程库,使用时需要#include <thread>头文件,该头文件主要包含了对线程的管理类std::thread以及其他管理线程相关的类。
    • 每个应用程序至少有一个进程,而每个进程至少有一个主线程,除了主线程外,在一个进程中还可以创建多个子线程。每个线程都需要一个入口函数,入口函数返回退出,该线程也会退出,主线程就是以main函数作为入口函数的线程。

    9.2.1  启动线程

    std::thread的构造函数需要的是可调用(callable)类型,除了函数外,还可以调用例如:lambda表达式、重载了()运算符的类的实例。

    #include <iostream>
    #include <thread>
    
    using namespace std;
    
    void output(int i)
    {
        cout << i << endl;
    }
    
    int main()
    {
        for (uint8_t i = 0; i < 4; i++)
        {
            //创建一个线程t,第一个参数为调用的函数,第二个参数为传递的参数
            thread t(output, i);
            //表示允许该线程在后台运行
            t.detach(); 
        }
        
        return 0;
    }
    

    在多线程并行的条件下,其输出结果不一定是顺序呢的输出1234,可能如下:

    多线程并行
     

    注意:

    • 把函数对象传入std::thread时,应传入函数名称(命名变量,如:output)而不加括号(临时变量,如:output())。
    • 当启动一个线程后,一定要在该线程thread销毁前,调用t.join()或者t.detach(),确定以何种方式等待线程执行结束:
      • detach方式,启动的线程自主在后台运行,当前的代码继续往下执行,不等待新线程结束。
      • join方式,等待关联的线程完成,才会继续执行join()后的代码。
      • 在以detach的方式执行线程时,要将线程访问的局部数据复制到线程的空间(使用按值传递),一定要确保线程没有使用局部变量的引用或者指针,除非你能肯定该线程会在局部作用域结束前执行结束。

    9.2.2  向线程传递参数

    向线程调用的函数只需要在构造thread的实例时,依次传入即可。

    thread t(output, arg1, arg2, arg3, ...);

    9.2.3  调用类成员函数

    class foo
    {
    public:
        void bar1(int n)
        {
            cout<<"n = "<<n<<endl;
        }
        static void bar2(int n)
        {
            cout<<"static function is running"<<endl;
            cout<<"n = "<<n<<endl;
        }
    };
    
    int main()
    {
        foo f;
        thread t1(&foo::bar1, &f, 5); //注意在调用非静态类成员函数时,需要加上实例变量。
        t1.join();
        
        thread t2(&foo::bar2, 4);
        t2.join();
    }

    9.2.4  转移线程的所有权

    thread是可移动的(movable)的,但不可复制的(copyable)。可以通过move来改变线程的所有权,灵活的决定线程在什么时候join或者detach。

    thread t1(f1);
    thread t3(move(t1));
    

    将线程从t1转移给t3,这时候t1就不再拥有线程的所有权,调用t1.join或t1.detach会出现异常,要使用t3来管理线程。这也就意味着thread可以作为函数的返回类型,或者作为参数传递给函数,能够更为方便的管理线程。

    9.2.5  线程标识的获取

    线程的标识类型为std::thread::id,有两种方式获得到线程的id:

    1. 通过thread的实例调用get_id()直接获取;
    2. 在当前线程上调用this_thread::get_id()获取。

    9.2.6  线程暂停

    如果让线程从外部暂停会引发很多并发问题,这也是为什么std::thread没有直接提供pause函数的原因。如果线程在运行过程中,确实需要停顿,就可以用this_thread::sleep_for。

    void threadCaller()
    {
        this_thread::sleep_for(chrono::seconds(3)); //此处线程停顿3秒。
        cout<<"thread pause for 3 seconds"<<endl;
    }
    
    int main()
    {
        thread t(threadCaller);
        t.join();
    }

    9.2.7  异常情况下等待线程完成

    为了避免主线程出现异常时将子线程终结,就要保证子线程在函数退出前完成,即在函数退出前调用join()。

    方法一:异常捕获

    void func() {
        thread t([]{
            cout << "hello C++ 11" << endl;
        });
    
        try
        {
            do_something_else();
        }
        catch (...)
        {
            t.join();
            throw;
        }
        t.join();
    }
    

    方法二:资源获取即初始化(RAII)

    class thread_guard
    {
        private:
            thread &t;
        public:
            /*加入explicit防止隐式转换,explicit仅可加在带一个参数的构造方法上,如:Demo test; test = 12.2;
            这样的调用就相当于把12.2隐式转换为Demo类型,加入explicit就禁止了这种转换。*/
            explicit thread_guard(thread& _t) {
                t = _t;
            }
    
            ~thread_guard()
            {
                if (t.joinable())
                    t.join();
            }
    
            thread_guard(const thread_guard&) = delete;  //删除默认拷贝构造函数
            thread_guard& operator=(const thread_guard&) = delete;  //删除默认赋值运算符
    };
    
    void func(){
    
        thread t([]{
            cout << "Hello thread" <<endl ;
        });
    
        thread_guard guard(t);
    }
    

    无论是何种情况,当函数退出时,对象guard调用其析构函数销毁,从而能够保证join一定会被调用。

    9.3  线程的同步与互斥

    线程之间通信的两个基本问题是互斥和同步:

    • 线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
    • 线程互斥是指对于共享的操作系统资源,在各线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。

    线程互斥是一种特殊的线程同步。实际上,同步和互斥对应着线程间通信发生的两种情况:

    • 当一个线程需要将某个任务已经完成的情况通知另外一个或多个线程时;
    • 当有多个线程访问共享资源而不使资源被破坏时。

    在WIN32中,同步机制主要有以下几种:

    1. 临界区(Critical Section):通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。  
    2. 事件(Event):用来通知线程有一些事件已发生,从而启动后继任务的开始。
    3. 信号量(Semaphore):为控制一个具备有限数量用户资源而设计。  
    4. 互斥量(Mutex):为协调一起对一个共享资源的单独访问而设计的。   

    9.3.1  临界区

    临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。

    临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。

    #include "stdafx.h"
    #include<windows.h>
    #include<iostream>
    using namespace std;
     
    int number = 1; //定义全局变量
    CRITICAL_SECTION Critical;      //定义临界区句柄
     
    unsigned long __stdcall ThreadProc1(void* lp)
    {
        while (number < 100)
        {
            EnterCriticalSection(&Critical);
            cout << "thread 1 :"<<number << endl;
            ++number;
            _sleep(100);
            LeaveCriticalSection(&Critical);
        }
     
        return 0;
    }
     
    unsigned long __stdcall ThreadProc2(void* lp)
    {
        while (number < 100)
        {
            EnterCriticalSection(&Critical);
            cout << "thread 2 :"<<number << endl;
            ++number;
            _sleep(100);
            LeaveCriticalSection(&Critical);
        }
     
        return 0;
    }
     
    int main()
    {
        InitializeCriticalSection(&Critical);   //初始化临界区对象
     
        CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
        CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
     
        Sleep(10*1000);
     
        system("pause");
        return 0;
    }

    9.3.2  事件

    事件对象能够通过通知操作的方式来保持线程的同步,并且能够实现不同进程中的线程同步操作。事件可以处于激发状态(signaled or true)或未激发状态(unsignal or false)。根据状态变迁方式的不同,事件可分为两类:

    1. 手动设置:这种对象只可能用程序手动设置,在需要该事件或者事件发生时,采用SetEvent及ResetEvent来进行设置。
    2. 自动恢复:一旦事件发生并被处理后,自动恢复到没有事件状态,不需要再次设置。

    使用”事件”机制应注意以下事项:

    1. 如果跨进程访问事件,必须对事件命名,在对事件命名的时候,要注意不要与系统命名空间中的其它全局命名对象冲突;
    2. 事件是否要自动恢复;
    3. 事件的初始状态设置。
    #include "stdafx.h"
    #include<windows.h>
    #include<iostream>
    using namespace std;
     
    int number = 1; //定义全局变量
    HANDLE hEvent;  //定义事件句柄
     
    unsigned long __stdcall ThreadProc1(void* lp)
    {
        while (number < 100)
        {
            WaitForSingleObject(hEvent, INFINITE);  //等待对象为有信号状态
            cout << "thread 1 :"<<number << endl;
            ++number;
            _sleep(100);
            SetEvent(hEvent);
        }
     
        return 0;
    }
     
    unsigned long __stdcall ThreadProc2(void* lp)
    {
        while (number < 100)
        {
            WaitForSingleObject(hEvent, INFINITE);  //等待对象为有信号状态
            cout << "thread 2 :"<<number << endl;
            ++number;
            _sleep(100);
            SetEvent(hEvent);
        }
     
        return 0;
    }
     
    int main()
    {
        CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
        CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
        hEvent = CreateEvent(NULL, FALSE, TRUE, "event");
     
        Sleep(10*1000);
     
        system("pause");
        return 0;
    }

    由于event对象属于内核对象,故进程B可以调用OpenEvent函数通过对象的名字获得进程A中event对象的句柄,然后将这个句柄用于ResetEvent、SetEvent和WaitForMultipleObjects等函数中。此法可以实现一个进程的线程控制另一进程中线程的运行,例如:

    HANDLE hEvent=OpenEvent(EVENT_ALL_ACCESS,true,"MyEvent");
    ResetEvent(hEvent);

    9.3.3  信号量

    信号量对象对线程的同步方式和前面几种方法不同,信号允许多个线程同时使用共享资源,但是需要限制在同一时刻访问此资源的最大线程数目。

    用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数配置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就能够发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。 

    信号量包含的几个操作原语:   

    • CreateSemaphore() 创建一个信号量   
    • OpenSemaphore() 打开一个信号量   
    • ReleaseSemaphore() 释放信号量   
    • WaitForSingleObject() 等待信号量  
    #include "stdafx.h"
    #include<windows.h>
    #include<iostream>
    using namespace std;
     
    int number = 1; //定义全局变量
    HANDLE hSemaphore;  //定义信号量句柄
     
    unsigned long __stdcall ThreadProc1(void* lp)
    {
        long count;
        while (number < 100)
        {
            WaitForSingleObject(hSemaphore, INFINITE);  //等待信号量为有信号状态
            cout << "thread 1 :"<<number << endl;
            ++number;
            _sleep(100);
            ReleaseSemaphore(hSemaphore, 1, &count);
        }
     
        return 0;
    }
     
    unsigned long __stdcall ThreadProc2(void* lp)
    {
        long count;
        while (number < 100)
        {
            WaitForSingleObject(hSemaphore, INFINITE);  //等待信号量为有信号状态
            cout << "thread 2 :"<<number << endl;
            ++number;
            _sleep(100);
            ReleaseSemaphore(hSemaphore, 1, &count);
        }
     
        return 0;
    }
     
    int main()
    {
        hSemaphore = CreateSemaphore(NULL, 1, 100, "sema");
     
        CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
        CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
     
        Sleep(10*1000);
     
        system("pause");
        return 0;
    }

    9.3.4  互斥量

    采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享。

    互斥量包含的几个操作原语:   

    • CreateMutex() 创建一个互斥量   
    • OpenMutex() 打开一个互斥量   
    • ReleaseMutex() 释放互斥量   
    • WaitForMultipleObjects() 等待互斥量对象  
    #include "stdafx.h"
    #include<windows.h>
    #include<iostream>
    using namespace std;
     
    int number = 1; //定义全局变量
    HANDLE hMutex;  //定义互斥对象句柄
     
    unsigned long __stdcall ThreadProc1(void* lp)
    {
        while (number < 100)
        {
            WaitForSingleObject(hMutex, INFINITE);
            cout << "thread 1 :"<<number << endl;
            ++number;
            _sleep(100);
            ReleaseMutex(hMutex);
        }
     
        return 0;
    }
     
    unsigned long __stdcall ThreadProc2(void* lp)
    {
        while (number < 100)
        {
            WaitForSingleObject(hMutex, INFINITE);
            cout << "thread 2 :"<<number << endl;
            ++number;
            _sleep(100);
            ReleaseMutex(hMutex);
        }
     
        return 0;
    }
     
    int main()
    {
        hMutex = CreateMutex(NULL, false, "mutex");     //创建互斥对象
     
        CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);
        CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);
     
        Sleep(10*1000);
     
        system("pause");
        return 0;
    }

    9.4  C++中的几种锁

    在9.3.4中我们讲到了互斥量,其中CreateMutex等是Win32 api函数,而本节要介绍的std :: mutex来自C++标准库。

    在C++11中线程之间的锁有:互斥锁、条件锁、自旋锁、读写锁、递归锁

    9.4.1  互斥锁

    互斥锁是一种简单的加锁的方法来控制对共享资源的访问。

    通过std::mutex可以方便的对临界区域加锁,std::mutex类定义于mutex头文件,是用于保护共享数据避免从多个线程同时访问的同步原语,它提供了lock、try_lock、unlock等几个接口。使用方法如下:

    std::mutex mtx;
    mtx.lock()
    do_something...;    //共享的数据
    mtx.unlock();

    mutex的lock和unlock必须成对调用,lock之后忘记调用unlock将是非常严重的错误,再次lock时会造成死锁。

    此时可以使用类模板std::lock_guard,通过RAII机制在其作用域内占有mutex,当程序流程离开创建lock_guard对象的作用域时,lock_guard对象被自动销毁并释放mutex。lock_guard构造时还可以传入一个参数adopt_lock或者defer_lock。adopt_lock表示是一个已经锁上了锁,defer_lock表示之后会上锁的锁。

    std::mutex mtx;
    std::lock_guard<std::mutex> guard(mtx);
    do_something...;    //共享的数据
    

    lock_guard类最大的缺点也是简单,没有给程序员提供足够的灵活度,因此C++11定义了另一个unique_guard类。这个类和lock_guard类似,也很方便线程对互斥量上锁,但它提供了更好的上锁和解锁控制,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。

    #include <iostream>       // std::cout
    #include <thread>         // std::thread
    #include <mutex>          // std::mutex, std::unique_lock
    #include <vector>
    
    std::mutex mtx;           // mutex for critical section
    std::once_flag flag;
    
    void print_block (int n, char c) {
        //unique_lock有多组构造函数, 这里std::defer_lock不设置锁状态
        std::unique_lock<std::mutex> my_lock (mtx, std::defer_lock);
        //尝试加锁, 如果加锁成功则执行
        //(适合定时执行一个job的场景, 一个线程执行就可以, 可以用更新时间戳辅助)
        if(my_lock.try_lock()){
            for (int i=0; i<n; ++i)
                std::cout << c;
            std::cout << '\n';
        }
    }
    
    void run_one(int &n){
        std::call_once(flag, [&n]{n=n+1;}); //只执行一次, 适合延迟加载; 多线程static变量情况
    }
    
    int main () {
        std::vector<std::thread> ver;
        int num = 0;
        for (auto i = 0; i < 10; ++i){
            ver.emplace_back(print_block,50,'*');
            ver.emplace_back(run_one, std::ref(num));
        }
    
        for (auto &t : ver){
            t.join();
        }
        std::cout << num << std::endl;
        return 0;
    }

    unique_lock比lock_guard使用更加灵活,功能更加强大,但使用unique_lock需要付出更多的时间、性能成本。

    9.4.2  条件锁

    条件锁就是所谓的条件变量,当某一线程满足某个条件时,可以使用条件变量令该程序处于阻塞状态;一旦该条件状态发生变化,就以“信号量”的方式唤醒一个因为该条件而被阻塞的线程。

    最为常见就是在线程池中,起初没有任务时任务队列为空,此时线程池中的线程因为“任务队列为空”这个条件处于阻塞状态。一旦有任务进来,就会以信号量的方式唤醒一个线程来处理这个任务。

    • 头文件:<condition_variable>
    • 类型:std::condition_variable(只与std::mutex一起工作)、std::condition_variable_any(可与符合类似互斥元的最低标准的任何东西一起工作)。
    std::deque<int> q;
    std::mutex mu;
    std::condition_variable cond;
    
    void function_1() //生产者
    {
        int count = 10;
        while (count > 0) 
        {
            std::unique_lock<std::mutex> locker(mu);
            q.push_front(count);
            locker.unlock();
            cond.notify_one();  // Notify one waiting thread, if there is one.
            std::this_thread::sleep_for(std::chrono::seconds(1));
            count--;
        }
    }
    
    void function_2() //消费者
    {
        int data = 0;
        while (data != 1) 
        {
            std::unique_lock<std::mutex> locker(mu);
            while (q.empty())
                cond.wait(locker); // Unlock mu and wait to be notified
            data = q.back();
            q.pop_back();
            locker.unlock();
            std::cout << "t2 got a value from t1: " << data << std::endl;
        }
    }
    int main() 
    {
        std::thread t1(function_1);
        std::thread t2(function_2);
        t1.join();
        t2.join();
        return 0;
    }

    上面是一个生产者-消费者模型,软件开启后,消费者线程进入循环,在循环里获取锁,如果消费品队列为空则wait,wait会自动释放锁;此时消费者已经没有锁了,在生产者线程里,获取锁,然后往消费品队列生产产品,释放锁,然后notify告知消费者退出wait,消费者重新获取锁,然后从队列里取消费品。

    9.4.3  自旋锁

    当发生阻塞时,互斥锁会让CPU去处理其他的任务,而自旋锁则会让CPU一直不断循环请求获取这个锁。由此可见“自旋锁”是比较耗费CPU的。在C++中我们可以通过原子操作实现自旋锁:

    //使用std::atomic_flag的自旋锁互斥实现
    class spinlock_mutex{
    private:
        std::atomic_flag flag;
    public:
        spinlock_mutex():flag(ATOMIC_FLAG_INIT) {}
        void lock()
        {
            while(flag.test_and_set(std::memory_order_acquire));
        }
        void unlock()
        {
            flag.clear(std::memory_order_release);
        }
    }

    9.4.4  读写锁

    说到读写锁我们可以借助于“读者-写者”问题进行理解。

    计算机中某些数据被多个进程共享,对数据库的操作有两种:一种是读操作,就是从数据库中读取数据不会修改数据库中内容;另一种就是写操作,写操作会修改数据库中存放的数据。因此可以得到我们允许在数据库上同时执行多个“读”操作,但是某一时刻只能在数据库上有一个“写”操作来更新数据。这就是一个简单的读者-写者模型。

    头文件:boost/thread/shared_mutex.cpp
    类型:boost::shared_lock、boost::shared_mutex

    shared_mutex比一般的mutex多了函数lock_shared() / unlock_shared(),允许多个(读者)线程同时加锁和解锁;而shared_lock则相当于共享版的lock_guard。对于shared_mutex使用lock_guard或unique_lock就可以达到写者线程独占锁的目的。

    读写锁的特点:

    1. 如果一个线程用读锁锁定了临界区,那么其他线程也可以用读锁来进入临界区,这样可以有多个线程并行操作。这个时候如果再用写锁加锁就会发生阻塞。写锁请求阻塞后,后面继续有读锁来请求时,这些后来的读锁都将会被阻塞。这样避免读锁长期占有资源,防止写锁饥饿。
    2. 如果一个线程用写锁锁住了临界区,那么其他线程无论是读锁还是写锁都会发生阻塞。

    9.4.5  递归锁

    递归锁又称可重入锁,在同一个线程在不解锁的情况下,可以多次获取锁定同一个递归锁,而且不会产生死锁。递归锁用起来固然简单,但往往会隐藏某些代码问题。比如调用函数和被调用函数以为自己拿到了锁,都在修改同一个对象,这时就很容易出现问题。

    9.5  C++中的原子操作

    9.5.1  atomic模版函数

    为了避免多个线程同时修改全局变量,C++11除了提供互斥量mutex这种方法以外,还提供了atomic模版函数。使用atomic可以避免使用锁,而且更加底层,比mutex效率更高。

    #include <thread>
    #include <iostream>
    #include <vector>
    #include <atomic>
    
    using namespace std;
    
    void func(int& counter)
    {
        for (int i = 0; i < 100000; ++i)
        {
            ++counter;
        }
    }
    
    int main()
    {
        //atomic<int> counter(0);
        atomic_int counter(0); //新建一个整型原子counter,将counter初始化为0
        //int counter = 0;
        vector<thread> threads;
        for (int i = 0; i < 10; ++i)
        {
            threads.push_back(thread(func, ref(counter)));
        }
        for (auto& current_thread : threads)
        {
            current_thread.join();
        }
        cout << "Result = " << counter << '\n';
        return 0;
    }

    为了避免多个线程同时修改了counter这个数导致出现错误,只需要把counter的原来的int型,改为atomic_int型就可以了,非常方便,也不需要用到锁。

    9.5.2  std::atomic_flag

    std::atomic_flag是一个原子型的布尔变量,只有两个操作:

    1)test_and_set,如果atomic_flag 对象已经被设置了,就返回True,如果未被设置,就设置之然后返回False

    2)clear,把atomic_flag对象清掉

    注意这个所谓atomic_flag对象其实就是当前的线程。如果当前的线程被设置成原子型,那么等价于上锁的操作,对变量拥有唯一的修改权。调用clear就是类似于解锁。

    下面先看一个简单的例子,main() 函数中创建了 10 个线程进行计数,率先完成计数任务的线程输出自己的 ID,后续完成计数任务的线程不会输出自身 ID:

    #include <iostream>              // std::cout
    #include <atomic>                // std::atomic, std::atomic_flag, ATOMIC_FLAG_INIT
    #include <thread>                // std::thread, std::this_thread::yield
    #include <vector>                // std::vector
    
    std::atomic<bool> ready(false);    // can be checked without being set
    std::atomic_flag winner = ATOMIC_FLAG_INIT;    // always set when checked
    
    void count1m(int id)
    {
        while (!ready) {
            std::this_thread::yield();
        } // 等待主线程中设置 ready 为 true.
    
        for (int i = 0; i < 1000000; ++i) {
        } // 计数.
    
        // 如果某个线程率先执行完上面的计数过程,则输出自己的 ID.
        // 此后其他线程执行 test_and_set 是 if 语句判断为 false,
        // 因此不会输出自身 ID.
        if (!winner.test_and_set()) {
            std::cout << "thread #" << id << " won!\n";
        }
    };
    
    int main()
    {
        std::vector<std::thread> threads;
        std::cout << "spawning 10 threads that count to 1 million...\n";
        for (int i = 1; i <= 10; ++i)
            threads.push_back(std::thread(count1m, i));
        ready = true;
    
        for (auto & th:threads)
            th.join();
    
        return 0;
    }

    再来一个例子:

    #include <iostream>
    #include <atomic>
    #include <vector>
    #include <thread>
    #include <sstream>
    
    
    std::atomic_flag lock = ATOMIC_FLAG_INIT; //初始化原子flag
    std::stringstream  stream;
    
    void append_number(int x)
    {
        while(lock.test_and_set()); //如果原子flag未设置,那么返回False,就继续后面的代码。否则一直返回True,就一直停留在这个循环。
        stream<<"thread#" <<x<<'\n';
        lock.clear(); //去除flag的对象
    }
    
    int main()
    {
        std::vector<std::thread> threads;
        for(int i=0;i<10;i++)
            threads.push_back(std::thread(append_number, i));
        
        for(auto& th:threads)
            th.join();
        std::cout<<stream.str()<<'\n';
    }

    9.6  相关面试题

    Q:C++怎么保证线程安全

    A:

    Q:悲观锁和乐观锁

    A:悲观锁:悲观锁是就是悲观思想,即认为读少写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block 直到拿到锁。

    乐观锁:乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复【读 - 比较 - 写】的操作。

    Q:什么是死锁

    A:所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

    Q:死锁形成的必要条件

    A:

    产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生

    • 互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
    • 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
    • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
    • 循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合 {Pl, P2, …, pn},其中 Pi 等 待的资源被 P (i+1) 占有(i=0, 1, …, n-1),Pn 等待的资源被 P0 占有

    Q:什么是活锁

    A:活锁和死锁在表现上是一样的两个线程都没有任何进展,但是区别在于:死锁,两个线程都处于阻塞状态而活锁并不会阻塞,而是一直尝试去获取需要的锁,不断的 try,这种情况下线程并没有阻塞所以是活的状态,我们查看线程的状态也会发现线程是正常的,但重要的是整个程序却不能继续执行了,一直在做无用功。

    Q:公平锁与非公平锁

    A:公平锁:是指多个线程在等待同一个锁时,必须按照申请锁的先后顺序来一次获得锁。

    非公平锁:理解了公平锁,非公平锁就很好理解了,它无非就是不用排队,当餐厅里的人出来后将钥匙往地上一扔,谁抢到算谁的。

     

    展开全文
  • 多线程面试题汇总(一)

    万次阅读 2018-07-21 14:53:00
    本文是作者整理的个人笔记,文中可能引用到其他人的成果...同一进程的线程共享一块内存空间和一组系统资源,线程本身有一个供程序执行时的堆栈。线程之间切换快,无需陷入内核态。 线程状态 阻塞状态分分为...
  • C++11多线程实现的一道面试题

    千次阅读 2018-06-15 12:40:14
    这里涉及到的问题是主线程和子线程之间的切换,也就是说子线程跑一下,然后阻塞,主线程再跑一下,然后在阻塞,如此各循环 50次,注意这里的“各循环”,因为主线程和子线程各自的循环是没有关系的,而是内部各自...
  • c++面试题线程与进程篇)

    万次阅读 多人点赞 2018-02-06 16:23:04
    ●多进程和多线程的区别 进程它是具有独立地址空间的,优点就是隔离度好,稳定,因为它是操作系统管理的,进程和进程之间是逻辑隔离的,只要操作系统不出问题的话,一个进程的错误一般不会影响到其它进程,缺点就是...
  • 第一线程的基本概念、线程的基本状态及状态之间的关系? 线程,有时称为轻量级进程,是CPU使用的基本单元;它由线程ID、程序计数器、寄存器集合和堆栈组成。它与属于同一进程的其他线程共享其代码段、数据段和...
  • 多线程经典面试题总结

    千次阅读 2016-03-14 13:38:56
    第一线程的基本概念、线程的基本状态及状态之间的关系?  线程是cpu使用的基本单元,它由线程ID、程序计数器、寄存器集合和堆栈组成。它与属于同一进程的其他线程共享其代码段、数据段和其他操作系统资源...
  • 题目:子线程循环  10  次,接着主线程循环 ...//被阻塞的线程唤醒后lk.lock()恢复在调用wait前的状态 } } int main(){ thread child(fun,10); fun(100); child.join(); return 0; }
  • 首先我们来分析一下这道...(是个刚入门的小白,分析的不好请见谅) 1、由于子线程需要循环10次不受主线程干扰,而主线程需要循环100次不受子线程干扰,所以显然,在他们进入循环的时候需要一个锁把这段循环锁住....
  • 说明:C++程序员面试题目总结(涉及C++基础知识、多线程多进程、TCP/IP网络编程、Linux操作、数据结构与算法) 内容来自作者看过的帖子或者看过的文章,个人整理自互联网,如有侵权,请联系作者删除。 作者为工作两...
  • C++多线程编程<一>之常见面试问题

    千次阅读 2013-12-30 17:16:00
     第五多线程同步和互斥有何异同,在什么情况下分别使用他们?举例说明。 同步是一种特殊的互斥。当访问资源量存在先后的顺序的时候使用同步,当需要独占试访问资源时使用互斥。 如一个生产者和多个消费者...
  • C++多线程面试经典问题

    千次阅读 2017-09-17 21:01:34
    1、基本概念  详见:线程和进程关系和区别、同步和互斥、进程间通信 2、以下多线程对int型变量x的操作,哪几个... 详见:多线程多线程中的隐蔽问题揭秘 3、多线程中栈与堆是公有的还是私有的 (C)  
  • C++11多线程编程

    万人学习 2018-09-06 14:34:58
    本课程,讲解的重点定位在c++11新标准中的多线程开发部分,同时,老师还会结合自己的经验把多线程的讲解进一步拓展到一个比较大的范畴,因为无论是c++11多线程开发还是各种其他的多线程开发实现方法,都有很多类似的...
  • 多线程和高并发的常见面试题整理

    千次阅读 2020-07-12 11:46:03
    1.线程实现方式 1继承Thread类 定义Thread类的子类,并重写Thread类的run()方法,创建子类对象(即线程对象),调用线程对象的start()方法来启动该线程 2.实现Runnable接口 并重写该接口的run()方法,该run()方法...
  • 史上最全Java多线程面试题及答案

    万次阅读 多人点赞 2018-08-20 11:17:08
    这篇文章主要是对多线程的问题进行总结的,因此罗列了40个多线程的问题。 这些多线程的问题,有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网友也都...
  • Java基础知识面试题(2020最新版)

    万次阅读 多人点赞 2020-02-19 12:11:27
    文章目录Java概述何为编程什么是Javajdk1.5之后的三大版本JVM、JRE和JDK的关系什么是跨平台性?原理是什么Java语言有哪些...Java和C++的区别Oracle JDK 和 OpenJDK 的对比基础语法数据类型Java有哪些数据类型switc...
  • 多线程经典面试题59问。 1.什么是活锁、饥饿、无锁、死锁? 死锁、活锁、饥饿是关于多线程是否活跃出现的运行阻塞障碍问题,如果线程出现 了这三种情况,即线程不再活跃,不能再正常地执行下去了。 死锁 死锁是...
  • c++多线程编程

    2018-09-18 23:31:14
    本文简介了pthread的用法,介绍了多核多线程编程的入门知识
  • Python线程面试题

    千次阅读 2018-08-16 15:38:06
    1.什么是GIL,怎么解决GIL? 在python的原始解释器CPython中存在着GIL(Global Interpreter Lock,全局解释器锁),...所以,虽然CPython的线程库直接封装了系统的原生线程,但CPython整体作为一个进程,同一时间只...
  • 多线程常见的面试题

    千次阅读 2020-09-16 16:21:58
    多线程常见的面试题: 1. 什么是线程和进程? 线程与进程的关系,区别及优缺点? 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。 ...
  • Java面试题大全(2020版)

    万次阅读 多人点赞 2019-11-26 11:59:06
    发现网上很Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家有帮助哈~ 本套Java面试题大全,全的不能再全,哈哈~ 一、Java 基础 1. JDK 和 JRE 有什么区别? JDK:Java ...
  • Java多线程面试题

    万次阅读 2020-10-25 15:56:40
    sleep 方法: 是 Thread 类的静态方法,当前线程将睡眠 n 毫秒,线程进入阻塞状态。当睡眠时间到了,会解除阻塞,进行可运行状态,等待 CPU 的到来。睡眠不释放锁(如果有的话); wait 方法: 是 Object 的方法...
  • 1、什么是进程,什么是线程,为什么需要多线程编程? 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是操作系统进行资源分配和调度的一个独立单位; 线程是进程的一个实体,是CPU调度和分派的...
  • c++并发面试题

    千次阅读 2019-05-13 18:22:13
    50个多线程面试题,你会多少? https://blog.csdn.net/cmyperson/article/details/79610870 多线程的40个面试题总结(上) https://blog.csdn.net/u012459345/article/details/51179578 Linux的多线程的一些...
  • C++面试题二---线程与进程

    千次阅读 2018-08-14 20:55:34
    参考资料: https://blog.csdn.net/lwgechen/article/details/77152319 https://blog.csdn.net/jjj19891128/article/details/24393661 ...经典的线程与进程的面试题 1、...
  • 多线程经典面试题

    千次阅读 2018-03-02 16:08:42
    多线程在笔试面试中经常出现,下面列出一些公司的多线程笔试面试题。首先是一些概念性的问答题,这些是多线程的基础知识,经常出现在面试中的第一轮面试(我参加2011年腾讯研究院实习生招聘时就被问到了几个概念性...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 34,459
精华内容 13,783
关键字:

c++多线程面试题

c++ 订阅