事件、线程同步

1.内核对象
内核对象是由操作系创建和维护的,在程序的高2g内存中创建;
进程可以共享内核对象;
常见的内核对象:进程、线程、文件、文件映射、事件、互斥体等等                
 
1)内核对象的创建
各种内核对象有各自的创建API函数;
内核对象是由操作系统创建的,三环程序只能用API函数告诉系统需要创建一个内核对象;
例如:创建事件和互斥体
HANDLE g_hEvent = CreateEvent(NULL, TRUE, FALSE, "XYZ");                        
HANDLE g_hMutex = CreateMutex(NULL,FALSE, "XYZ");                     
windows在创建完内核对象后会返回该内核对象的句柄;
句柄实际上是指针的指针;
不直接返回内核对象地址的原因是为了安全;如果高2g的程序通过地址直接修改了内核对象,可能会导致系统崩溃;
 
2)获取内核对象
获取事件的API
HANDLE OpenEvent(                
  DWORD dwDesiredAccess,  // access                
  BOOL bInheritHandle,    // inheritance option                
  LPCTSTR lpName          // object name                
);            
实例:获取事件和互斥体
HANDLE g_hEvent = OpenEvent(EVENT_ALL_ACCESS, FALSE, "XYZ");                        
HANDLE g_hMutex = OpenMutex(MUTEX_ALL_ACCESS,FALSE, "XYZ");   

 

3)内核对象销毁
API函数:
BOOL CloseHandle(HANDLE hobj);
只是告诉操作系统,这个内核对象不用了,实际上该内核对象是否销毁还要看操作系统;
在创建完内核对象后,低2g会有一个结构体保存内核对象的信息;
结构体中有一个成员为计数器,每当有一个进程用openXXX调用内核对象时,计数器加1,调用CloseHandle时计数器减1;
当计数器为0时表示没有进程使用内核对象了,操作系统将销毁该对象;
 
1】当没有其他程序引用时,系统会销毁内核对象(使用数量).                        
2】内核对象的生命周期,可能比创建它的对象要长. (其它进程调用open方法得到了内核进程的句柄时)                       
 
2.事件
事件是一种内核对象;
 
1)创建事件
HANDLE CreateEvent(                
  LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性 NULL时为系统默认                
  BOOL bManualReset,                       // TRUE 通过调用ResetEvent将事件对象标记为未通知                
  BOOL bInitialState,                      // TRUE 已通知状态  FALSE未通知状态                
  LPCTSTR lpName                           // 对象名称 以NULL结尾的字符串                
);          
bManualReset    ->如果为FALSE,只要有线程wait到了该事件,立马会将该事件的状态改回去变成未通知状态;为TRUE只能wait完后自己写代码来修改为未通知状态;
bInitialState        ->表示事件创建时是否为已通知状态;如果为TRUE有线程wait该事件时在创建时立马就wait到了;
 
2)事件控制
BOOL SetEvent(HANDLE hEvent);    //将对象设置为已通知

 

3)关闭事件句柄
BOOL CloseHandle(HANDLE hobj);

 

4)实例
创建一个窗口程序;
点击按钮go时会给文本框1赋值,然后开启3个线程分别将文本框1的值复制到文本框2、3、4中;
大体思路:
    在go的点击事件中创建一个线程1,用来给文本框1赋值;
    在线程1中创建一个事件a;然后在线程1中创建3个线程2、3、4;分别用来将文本框1中的值复制到文本框2、3、4中;
    线程2、3、4都调用wait函数等待事件a;当事件a变成已通知状态后,3个线程的wait都将被触发,完成复制操作;
 
代码:
#include<windows.h>
#include<stdio.h>
#include "resource.h"
 
HANDLE g_hEvent;                
                
HWND hEdit1;                
HWND hEdit2;                
HWND hEdit3;                
HWND hEdit4;                                                                
                
DWORD WINAPI ThreadProc2(LPVOID lpParameter)                
{                
    TCHAR szBuffer[10] = {0};            
                
    //当事件变成已通知时             
    WaitForSingleObject(g_hEvent, INFINITE);            
    //读取内容            
    GetWindowText(hEdit1,szBuffer,10);            
    SetWindowText(hEdit2,szBuffer);            
    return 0;            
}                
DWORD WINAPI ThreadProc3(LPVOID lpParameter)                
{                
    TCHAR szBuffer[10] = {0};            
    //当事件变成已通知时             
    WaitForSingleObject(g_hEvent, INFINITE);            
    //读取内容            
    GetWindowText(hEdit1,szBuffer,10);            
    SetWindowText(hEdit3,szBuffer);            
    return 0;            
}                
DWORD WINAPI ThreadProc4(LPVOID lpParameter)                
{                
    TCHAR szBuffer[10] = {0};            
    //当事件变成已通知时             
    WaitForSingleObject(g_hEvent, INFINITE);            
    //读取内容            
    GetWindowText(hEdit1,szBuffer,10);            
    SetWindowText(hEdit4,szBuffer);            
    return 0;            
}        
 
DWORD WINAPI ThreadProc1(LPVOID lpParameter)                
{                
    //创建事件            
    //默认安全属性  手动设置未通知状态(TRUE)  初始状态未通知 没有名字             
    g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);            
    HANDLE hThread[3];            
    //创建3个线程            
    hThread[0] = ::CreateThread(NULL, 0, ThreadProc2, NULL, 0, NULL);            
    hThread[1] = ::CreateThread(NULL, 0, ThreadProc3, NULL, 0, NULL);            
    hThread[2] = ::CreateThread(NULL, 0, ThreadProc4, NULL, 0, NULL);            
                
    //设置文本框的值            
    SetWindowText(hEdit1,"1000");            
                
    //设置事件为已通知            
    SetEvent(g_hEvent);            
                
    //等待线程结束 销毁内核对象            
    WaitForMultipleObjects(3, hThread, TRUE, INFINITE);              
    CloseHandle(hThread[0]);              
    CloseHandle(hThread[1]);            
    CloseHandle(hThread[2]);            
    CloseHandle(g_hEvent);              
                
    return 0;            
}        
  
//回调函数
BOOL CALLBACK DialogProc(                                    
                         HWND hwndDlg,  // handle to dialog box            
                         UINT uMsg,     // message            
                         WPARAM wParam, // first message parameter            
                         LPARAM lParam  // second message parameter            
                         )            
{    
    switch(uMsg)                                
    {    
    case WM_INITDIALOG :
        hEdit1 = GetDlgItem(hwndDlg, TXT_1);
        hEdit2 = GetDlgItem(hwndDlg, TXT_2);
        hEdit3 = GetDlgItem(hwndDlg, TXT_3);
        hEdit4 = GetDlgItem(hwndDlg, TXT_4);
        return TRUE;     
    case  WM_COMMAND :                                                                   
        switch (LOWORD (wParam))                            
        {
        case BTN_GO:  
            //额外创建一个线程来控制其它线程,防止主线程阻塞
            HANDLE hThread = ::CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);        
            //如果不在其他的地方引用它 关闭句柄                
            ::CloseHandle(hThread);            
            return TRUE;  
        return FALSE;    
        }                            
        break ;   
    case WM_CLOSE:
        EndDialog(hwndDlg, 0);
        return TRUE;        
    }                                    
    return FALSE ;                                
}     
 
 
//程序入口
int CALLBACK WinMain(
                     HINSTANCE hInstance,
                     HINSTANCE hPrevHinstance,
                     LPSTR lpCmdLine,
                     int nCmdShow
        ){  
    //创建对话框窗口
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogProc);    
    return 0;
}
结果:
因为事件的bManualReset为TRUE,其中一个线程等到事件后不会吧事件改为未通知状态,因此其它两个线程也会等到已通知状态的事件,所以3个文本框都复制成功;
如果将bManualReset设为FALSE,将只有一个文本框复制成功;
因为bManualReset位FALSE时,一个线程等到事件后,将会把事件改为未通知,其它线程将永远等不到已通知的事件而阻塞;
bManualReset为FALSE时的事件和互斥体的作用相似;只需要在线的功能执行完后手动调用SetEvent函数将事件重新设为已通知状态即可;
 
3.线程的同步
如果想多线程程序,需要解决两个问题:互斥和同步;
互斥是为了解决多个线程访问同一资源时的线程安全问题;
同步是为了让多个线程按自己需要的顺序来执行;
    例如让线程1和线程2交替执行,而不是让线程1执行多次再执行线程2;
 
线程同步的应用场景:
    例如线程a将文本读入内存;线程1分析文本中字数,线程2分析文本中的标点符号数;
    此时需要线程a执行完后线程1、2才能执行;
 
1)用临界区和互斥体实现同步
例如:
    有一个生产者线程和一个消费者线程;
    需求是生产者线程将全局变量的值设为1,然后消费者线程将读到全局变量的值将其设为0;
    用临界区和互斥体的作用差不多,都用来实现对全局变量的互斥访问;
    但临界区和互斥体并不能保证生产者和消费者线程的访问顺序;
    只能定义一个全局变量a用做标记,每次线程执行时先判断该标记的值来决定是否执行线程的功能;
    这种办法效率低下,例如生产者线程被分配了cpu资源,此时判断标记得知全局变量的值不为0,表示没必要修改;但分配 到的cpu资源不会释放;   
 
代码:
CRITICAL_SECTION g_cs;        
int g_Max = 10;        
int g_Number = 0;                              
//生产者线程函数          
DWORD WINAPI ThreadProduct(LPVOID pM)          
{          
    for (int i = 0; i < g_Max; i++)        
    {          
        //互斥的访问缓冲区          
        EnterCriticalSection(&g_cs);         
        g_Number = 1;
        DWORD id = GetCurrentThreadId();
        printf("生产者%d将数据%d放入缓冲区\n",id, g_Number);
        LeaveCriticalSection(&g_cs);         
        
    }          
    return 0;          
}          
//消费者线程函数        
DWORD WINAPI ThreadConsumer(LPVOID pM)          
{          
    for (int i = 0; i < g_Max; i++)        
    {          
        //互斥的访问缓冲区          
        EnterCriticalSection(&g_cs);          
        g_Number = 0;
        DWORD id = GetCurrentThreadId();
        printf("----消费者%d将数据%d放入缓冲区\n",id, g_Number);
    LeaveCriticalSection(&g_cs);     
    }          
    return 0;          
}          
        
int main(int argc, char* argv[])        
{        
    InitializeCriticalSection(&g_cs);    
    HANDLE hThread[2];         
        
    hThread[0] = ::CreateThread(NULL, 0, ThreadProduct, NULL, 0, NULL);         
    hThread[1] = ::CreateThread(NULL, 0, ThreadConsumer, NULL, 0, NULL);    
        
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);          
    CloseHandle(hThread[0]);          
    CloseHandle(hThread[1]);        
        
    //销毁     
    DeleteCriticalSection(&g_cs);          
    return 0;    
}        

 

2)用事件实现线程同步
事件可以做到线程互斥,也能做到简单的线程同步;
 
思路:
    创建两个事件:set和clear;
    两个事件的bManualReset都设为FALSE,表示不需要手动修改事件为未通知状态;
    set的bInitialState设为,TRUE表示当事件创建时就是已通知状态;
    生产者线程等待set事件;因为set线程创建时就已通知状态,开始就能将全局变量设为1;
    当生产者线程执行完后,将clear线程设为已通知状态;因为bManualReset位FALSE,set事件自动变为未通知状态,生产者线程阻塞;
    消费者线程等待clear事件;
    clear事件的bInitialState为FALSE,创建时为未通知状态,因此消费者线程将等待;
    直到生产者线程将clear事件变成了已通知后,消费者线程继续执行;
    消费者线程执行完后set事件自动变成未通知状态,消费者线程阻塞;
    在消费者线程执行完前,调用函数将set事件变成已通知状态,因此生产者线程又能继续执行;
    如此实现了两个线程的交替执行;
    这样实现的同步效率较高,当线程无法执行时会进入等待状态cpu不会给它分配资源;
 
代码:
HANDLE g_hSet, g_hClear;        
int g_Max = 10;        
int g_Number = 0;        
                           
//生产者线程函数          
DWORD WINAPI ThreadProduct(LPVOID pM)          
{          
    for (int i = 0; i < g_Max; i++)        
    {          
        WaitForSingleObject(g_hSet, INFINITE);          
        g_Number = 1;
        DWORD id = GetCurrentThreadId();
        printf("生产者%d将数据%d放入缓冲区\n",id, g_Number);
        SetEvent(g_hClear);           
    }          
    return 0;          
}          
//消费者线程函数        
DWORD WINAPI ThreadConsumer(LPVOID pM)          
{          
    for (int i = 0; i < g_Max; i++)        
    {          
        WaitForSingleObject(g_hClear, INFINITE);          
        g_Number = 0;
        DWORD id = GetCurrentThreadId();
        printf("----消费者%d将数据%d放入缓冲区\n",id, g_Number);
        SetEvent(g_hSet);           
    }          
    return 0;          
}          
        
int main(int argc, char* argv[])        
{        
        
    HANDLE hThread[2];         
        
    g_hSet = CreateEvent(NULL, FALSE, TRUE, NULL);      
    g_hClear = CreateEvent(NULL, FALSE, FALSE, NULL);         
        
    hThread[0] = ::CreateThread(NULL, 0, ThreadProduct, NULL, 0, NULL);         
    hThread[1] = ::CreateThread(NULL, 0, ThreadConsumer, NULL, 0, NULL);    
        
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);          
    CloseHandle(hThread[0]);          
    CloseHandle(hThread[1]);        
        
    //销毁     
    CloseHandle(g_hSet);      
    CloseHandle(g_hClear);          
        
    return 0;    
}        

 

 
 
 
 
posted @ 2020-01-04 10:34  L丶银甲闪闪  阅读(288)  评论(0编辑  收藏  举报