30win32编程基础——生产者消费者问题深入理解

问题:

  1个线程负责生产,1个线程负责消费。

#include <Windows.h>
#include "OutPut.h"

int share_res =0;
int Max = 10;
DWORD WINAPI ThreadProduce( __in  LPVOID lpParameter)
{
	for(int i=0;i<Max;i++)
	{
		share_res = 1;
		int id = GetCurrentThreadId();
		OutputDebugStringF("生产者线程%d生产数据%d放入缓冲区\n",id,share_res);
	}

	return 0;
}

DWORD WINAPI ThreadConsume(
  __in  LPVOID lpParameter
){
	for(int i=0;i<Max;i++)
	{
		share_res = 0;
		int id = GetCurrentThreadId();
		OutputDebugStringF("消费者线程%d消费从缓冲区中取出数据%d\n",id,share_res);
	}

	return 0;
}

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR    lpCmdLine,
                     int       nCmdShow)
{
	HANDLE h[2];
	h[0]  = CreateThread(NULL,NULL,ThreadProduce,(LPVOID)0,0,NULL);
	h[1]  =CreateThread(NULL,NULL,ThreadConsume,(LPVOID)1,0,NULL);

	WaitForMultipleObjects(2,h,TRUE,-1);

	CloseHandle(h[0]);
	CloseHandle(h[1]);
	
	system("pause");
	return 0;
}

首先看什么都不用的输出结果:

生产者线程19000生产数据1放入缓冲区

消费者线程7404消费从缓冲区中取出数据0

生产者线程19000生产数据1放入缓冲区

消费者线程7404消费从缓冲区中取出数据0

生产者线程19000生产数据1放入缓冲区

消费者线程7404消费从缓冲区中取出数据0

生产者线程19000生产数据1放入缓冲区

消费者线程7404消费从缓冲区中取出数据0

生产者线程19000生产数据1放入缓冲区

消费者线程7404消费从缓冲区中取出数据0

生产者线程19000生产数据1放入缓冲区

消费者线程7404消费从缓冲区中取出数据0

生产者线程19000生产数据1放入缓冲区

消费者线程7404消费从缓冲区中取出数据0

消费者线程7404消费从缓冲区中取出数据0

生产者线程19000生产数据1放入缓冲区

生产者线程19000生产数据1放入缓冲区

消费者线程7404消费从缓冲区中取出数据0

消费者线程7404消费从缓冲区中取出数据0

生产者线程19000生产数据1放入缓冲区

可以发现,中间部分会出现先消费后生产的问题,那使用临界区能解决这个问题吗?

#include <Windows.h>
#include "OutPut.h"

int share_res =0;
int Max = 10;
CRITICAL_SECTION cs;
DWORD WINAPI ThreadProduce( __in  LPVOID lpParameter)
{
    for(int i=0;i<Max;i++)
    {
        EnterCriticalSection(&cs);
        share_res = 1;
        int id = GetCurrentThreadId();
        OutputDebugStringF("生产者线程%d生产数据%d放入缓冲区\n",id,share_res);
        LeaveCriticalSection(&cs);
    }

    return 0;
}

DWORD WINAPI ThreadConsume(
  __in  LPVOID lpParameter
){
    for(int i=0;i<Max;i++)
    {
        EnterCriticalSection(&cs);
        share_res = 0;
        int id = GetCurrentThreadId();
        OutputDebugStringF("消费者线程%d消费从缓冲区中取出数据%d\n",id,share_res);
        LeaveCriticalSection(&cs);
    }

    return 0;
}

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR    lpCmdLine,
                     int       nCmdShow)
{
   InitializeCriticalSection(&cs); 

    HANDLE h[2];
    h[0]  = CreateThread(NULL,NULL,ThreadProduce,(LPVOID)0,0,NULL);
    h[1]  =CreateThread(NULL,NULL,ThreadConsume,(LPVOID)1,0,NULL);

    WaitForMultipleObjects(2,h,TRUE,-1);

    CloseHandle(h[0]);
    CloseHandle(h[1]);
    DeleteCriticalSection(&cs);

    system("pause");
    return 0;
}

查看输出结果:

生产者线程9396生产数据1放入缓冲区

生产者线程9396生产数据1放入缓冲区

生产者线程9396生产数据1放入缓冲区

生产者线程9396生产数据1放入缓冲区

生产者线程9396生产数据1放入缓冲区

生产者线程9396生产数据1放入缓冲区

生产者线程9396生产数据1放入缓冲区

消费者线程16920消费从缓冲区中取出数据0

消费者线程16920消费从缓冲区中取出数据0

消费者线程16920消费从缓冲区中取出数据0

消费者线程16920消费从缓冲区中取出数据0

消费者线程16920消费从缓冲区中取出数据0

消费者线程16920消费从缓冲区中取出数据0

消费者线程16920消费从缓冲区中取出数据0

消费者线程16920消费从缓冲区中取出数据0

消费者线程16920消费从缓冲区中取出数据0

消费者线程16920消费从缓冲区中取出数据0

生产者线程9396生产数据1放入缓冲区

线程 'Win32 线程' (0x4218) 已退出,返回值为 0 (0x0)。
生产者线程9396生产数据1放入缓冲区

生产者线程9396生产数据1放入缓冲区

线程 'Win32 线程' (0x24b4) 已退出,返回值为 0 (0x0)。

 输出是:先生产了7次,然后消费了10次,然后消费者进程先结束了。

使用临界区不行,那么使用互斥体可以实现生产者消费者问题吗?

#include <Windows.h>
#include "OutPut.h"

int share_res =0;
int Max = 10;
HANDLE hMutex;
DWORD WINAPI ThreadProduce( __in  LPVOID lpParameter)
{
    for(int i=0;i<Max;i++)
    {
        WaitForSingleObject(hMutex,-1);
        share_res = 1;
        int id = GetCurrentThreadId();
        OutputDebugStringF("生产者线程%d生产数据%d放入缓冲区\n",id,share_res);
        ReleaseMutex(hMutex);
    }

    return 0;
}

DWORD WINAPI ThreadConsume(
  __in  LPVOID lpParameter
){
    for(int i=0;i<Max;i++)
    {
        WaitForSingleObject(hMutex,-1);
        share_res = 0;
        int id = GetCurrentThreadId();
        OutputDebugStringF("消费者线程%d消费从缓冲区中取出数据%d\n",id,share_res);
        ReleaseMutex(hMutex);
    }

    return 0;
}

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR    lpCmdLine,
                     int       nCmdShow)
{
    hMutex = CreateMutex(NULL,NULL,TEXT("xyz"));

    HANDLE h[2];
    h[0]  = CreateThread(NULL,NULL,ThreadProduce,(LPVOID)0,0,NULL);
    h[1]  =CreateThread(NULL,NULL,ThreadConsume,(LPVOID)1,0,NULL);

    WaitForMultipleObjects(2,h,TRUE,-1);

    CloseHandle(h[0]);
    CloseHandle(h[1]);
    CloseHandle(hMutex);

    system("pause");
    return 0;
}

查看输出:

生产者线程17128生产数据1放入缓冲区

消费者线程15504消费从缓冲区中取出数据0

生产者线程17128生产数据1放入缓冲区

消费者线程15504消费从缓冲区中取出数据0

生产者线程17128生产数据1放入缓冲区

消费者线程15504消费从缓冲区中取出数据0

消费者线程15504消费从缓冲区中取出数据0

生产者线程17128生产数据1放入缓冲区

消费者线程15504消费从缓冲区中取出数据0

生产者线程17128生产数据1放入缓冲区

消费者线程15504消费从缓冲区中取出数据0

生产者线程17128生产数据1放入缓冲区

消费者线程15504消费从缓冲区中取出数据0

生产者线程17128生产数据1放入缓冲区

消费者线程15504消费从缓冲区中取出数据0

生产者线程17128生产数据1放入缓冲区

消费者线程15504消费从缓冲区中取出数据0

生产者线程17128生产数据1放入缓冲区

消费者线程15504消费从缓冲区中取出数据0

生产者线程17128生产数据1放入缓冲区

线程 'Win32 线程' (0x3c90) 已退出,返回值为 0 (0x0)。
线程 'Win32 线程' (0x42e8) 已退出,返回值为 0 (0x0)。

发现互斥体也不能解决生产者和消费者问题,为什么?

因此不管是临界区还是互斥体解决的是互斥问题,即:同一时间只有1个线程访问同1个共享资源,这样就会出现这种情况,刚消费者执行完,下一步本来该生产者执行了,

但是由于某些原因(CPU调度线程,不可控),生产者线程不执行,继续执行消费者线程,然后再去执行生产者线程。这样就导致多次执行消费者线程或者多次执行生产者线程。

以上是互斥。

解决不了同步问题,即:2个线程有个先后顺序交替执行。

最简单是就是使用--->事件(或者信号量)

我们定义2个事件,1个生产者事件,1个消费者事件。

刚开始生产者线程先执行,生产者执行完通知消费者执行;消费者执行完通知生产者执行。

这样依次交替执行。

#include <Windows.h>
#include "OutPut.h"

int share_res =0;
int Max = 10;
HANDLE hProEvent;
HANDLE hConEvent;
DWORD WINAPI ThreadProduce( __in  LPVOID lpParameter)
{
    for(int i=0;i<Max;i++)
    {
        WaitForSingleObject(hProEvent,-1);
        share_res = 1;
        int id = GetCurrentThreadId();
        OutputDebugStringF("生产者线程%d生产数据%d放入缓冲区\n",id,share_res);
        SetEvent(hConEvent);
    }

    return 0;
}

DWORD WINAPI ThreadConsume(
  __in  LPVOID lpParameter
){
    for(int i=0;i<Max;i++)
    {
        WaitForSingleObject(hConEvent,-1);
        share_res = 0;
        int id = GetCurrentThreadId();
        OutputDebugStringF("消费者线程%d消费从缓冲区中取出数据%d\n",id,share_res);
        SetEvent(hProEvent);
    }

    return 0;
}

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR    lpCmdLine,
                     int       nCmdShow)
{
    hProEvent = CreateEvent(NULL,FALSE,TRUE,TEXT("Procude"));//刚开始发信号,,遇到wait自动变为未通知状态
    hConEvent = CreateEvent(NULL,FALSE,FALSE,TEXT("Consume"));//刚开始不发信号,遇到wait自动变为未通知状态

    HANDLE h[2];
    h[0]  = CreateThread(NULL,NULL,ThreadProduce,(LPVOID)0,0,NULL);
    h[1]  =CreateThread(NULL,NULL,ThreadConsume,(LPVOID)1,0,NULL);

    WaitForMultipleObjects(2,h,TRUE,-1);

    CloseHandle(h[0]);
    CloseHandle(h[1]);
    CloseHandle(hProEvent);
    CloseHandle(hConEvent);

    system("pause");
    return 0;
}

查看输出结果:


生产者线程19516生产数据1放入缓冲区


消费者线程18396消费从缓冲区中取出数据0


生产者线程19516生产数据1放入缓冲区


消费者线程18396消费从缓冲区中取出数据0


生产者线程19516生产数据1放入缓冲区


消费者线程18396消费从缓冲区中取出数据0


生产者线程19516生产数据1放入缓冲区


消费者线程18396消费从缓冲区中取出数据0


生产者线程19516生产数据1放入缓冲区


消费者线程18396消费从缓冲区中取出数据0


生产者线程19516生产数据1放入缓冲区


消费者线程18396消费从缓冲区中取出数据0


生产者线程19516生产数据1放入缓冲区


消费者线程18396消费从缓冲区中取出数据0


生产者线程19516生产数据1放入缓冲区


消费者线程18396消费从缓冲区中取出数据0


生产者线程19516生产数据1放入缓冲区


消费者线程18396消费从缓冲区中取出数据0


生产者线程19516生产数据1放入缓冲区


消费者线程18396消费从缓冲区中取出数据0


线程 'Win32 线程' (0x4c3c) 已退出,返回值为 0 (0x0)。
线程 'Win32 线程' (0x47dc) 已退出,返回值为 0 (0x0)。

 

事件只能解决简单的线程同步问题,对于复杂的线程同步控制还解决不了问题,因此又引出信号量的问题。  

还一种办法,使用标志法:

  定义1个flag判断是否消费和生产。

#include <Windows.h>
#include "OutPut.h"

int share_res =0;
int Max = 10;
int flag = 1;
DWORD WINAPI ThreadProduce( __in  LPVOID lpParameter)
{
    int id = GetCurrentThreadId();
    for(int i=0;i<Max;i++)
    {
        if(flag)
        {
            share_res = 1;
            OutputDebugStringF("生产者线程%d第%d次生产数据%d放入缓冲区\n",id,i,share_res);
            flag=0;
            
        }else{
            OutputDebugStringF("生产者线程%d第%d次等待生产\n",id,i);
            Sleep(50);
        }
        
    }

    return 0;
}

DWORD WINAPI ThreadConsume(
  __in  LPVOID lpParameter
){
    int id = GetCurrentThreadId();
    for(int i=0;i<Max;i++)
    {
        if(flag == 0)
        {
            share_res = 0;
            OutputDebugStringF("消费者线程%d消费从缓冲区中取出数据%d\n",id,share_res);
            flag=1;
            
        }else
        {
            OutputDebugStringF("消费者线程%d第%d次等待消费\n",id,i);
            Sleep(50);
            
        }
    }

    return 0;
}

int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR    lpCmdLine,
                     int       nCmdShow)
{
    

    HANDLE h[2];
    h[0]  = CreateThread(NULL,NULL,ThreadProduce,(LPVOID)0,0,NULL);
    h[1]  =    CreateThread(NULL,NULL,ThreadConsume,(LPVOID)1,0,NULL);
    
    WaitForMultipleObjects(2,h,TRUE,-1);

    CloseHandle(h[0]);
    CloseHandle(h[1]);
    
    system("pause");
    return 0;
}

输出结果:

消费者线程15968第0次等待消费

生产者线程19416第0次生产数据1放入缓冲区

生产者线程19416第1次等待生产

生产者线程19416第2次等待生产

消费者线程15968消费从缓冲区中取出数据0

消费者线程15968第2次等待消费

生产者线程19416第3次生产数据1放入缓冲区

消费者线程15968第3次等待消费

生产者线程19416第4次等待生产

消费者线程15968消费从缓冲区中取出数据0

生产者线程19416第5次等待生产

消费者线程15968第5次等待消费

消费者线程15968第6次等待消费

生产者线程19416第6次生产数据1放入缓冲区

生产者线程19416第7次等待生产

生产者线程19416第8次等待生产

消费者线程15968消费从缓冲区中取出数据0

消费者线程15968第8次等待消费

生产者线程19416第9次生产数据1放入缓冲区

线程 'Win32 线程' (0x4bd8) 已退出,返回值为 0 (0x0)。
消费者线程15968消费从缓冲区中取出数据0

多几次执行结果:

生产者线程3724第0次生产数据1放入缓冲区

生产者线程3724第1次等待生产

消费者线程8640第0次等待消费

消费者线程8640消费第1次从缓冲区中取出数据0

生产者线程3724第2次等待生产

消费者线程8640第2次等待消费

消费者线程8640第3次等待消费

生产者线程3724第3次生产数据1放入缓冲区

生产者线程3724第4次等待生产

生产者线程3724第5次等待生产

消费者线程8640消费第4次从缓冲区中取出数据0

消费者线程8640第5次等待消费

生产者线程3724第6次生产数据1放入缓冲区

生产者线程3724第7次等待生产

消费者线程8640消费第6次从缓冲区中取出数据0

消费者线程8640第7次等待消费

生产者线程3724第8次生产数据1放入缓冲区

生产者线程3724第9次等待生产

消费者线程8640消费第8次从缓冲区中取出数据0

消费者线程8640第9次等待消费

 

posted @ 2023-11-13 16:21  一日学一日功  阅读(63)  评论(0)    收藏  举报