27.windows线程

1.windows线程

  windows线程是可以执行的代码的实例。系统以线程为单位调度程序。

  一个程序中可以有多个线程,实现多任务处理。

2.windows线程的特点

  (1)线程都有一个ID

  (2)线程具有自己的安全属性

  (3)每个线程都有自己的内存栈

  (4)每个线程都有自己的寄存器信息

3.进程多任务和线程多任务

  进程多任务:每个进程都使用私有的地址空间

  线程多任务:进程内的多个线程使用同一个地址空间

  线程调用:将CPU的执行时间划分为时间片,依次根据时间片执行不同的线程

  线程轮询:线程A -> 线程B -> 线程A ... 

4.线程使用

  (1)定义线程处理函数

     DWORD  WINAPI  ThreadProc( LPVOID lpParameter );  //创建线程时传递给线程的参数

  (2)创建线程

     HANDLE  CreateThread( LPSECURITY_ATTRIBUTES      lpThreadAttributes,  //安全属性

                                                 SIZE_T                                 dwStackSize,          //线程栈大小(小于1M都按1M算)

                                                 LPTHREAD_START_ROUTINE   lpStartAddress,      //线程处理函数地址

                                                 LPVOID                                 lpParameter,           //传给线程处理函数的参数

                                                 DWORD                                dwCreationFlags,     //创建方式

                                                 LPDWORD                             lpThreadId);           //创建成功,返回线程ID

     创建成功返回线程句柄

     创建方式(dwCreationFlags):0 - 创建之后线程立即执行

                                      CREATE_SUSPENDED - 创建之后线程属于挂起(休眠)状态

  (3)结束线程

     //结束指定线程

     BOOL  TerminateThread( HANDLE   hThread,         //线程句柄

                                                  DWORD   dwExitCode);   //退出码

     //结束所在线程

     VOID  ExitThread( DWORD  dwExitCode );

  (4)关闭线程句柄

     CloseHandle(将线程句柄置为-1,只是这个句柄不能用了,跟线程状态没有任何关系)

  (5)线程的挂起和执行

     //挂起

     DWORD  SuspendThread( HANDLE  hThread );

     //执行

     DWORD  ResumeThread( HANDLE  hThread );

  (6)线程的信息

     //获得当前线程的ID

     DWORD  GetCurrentThreadId( VOID ); 

     //获得当前线程的句柄

     HANDLE  GetCurrentThread( VOID );

     通过指定ID的线程获得线程句柄

     HANDLE OpenThread( DWORD   dwDesiredAccess,  //访问权限

                                              BOOL       bInheritHandle,     //继承标识

                                              DWORD   dwThreadId);        //线程ID

5.多线程问题

  线程A -> 线程B -> 线程A ...,例如:线程A打印"********\n",线程B打印"--------\n",会出现错乱情况:

  ****----****

  --********

  -------*****

  .......,那么,出现的原因是:

  当线程A执行printf输出时,如果线程A的执行时间结束,系统会将线程A的相关信息(栈、寄存器)压栈保护,

  同时将线程B相关信息恢复,然后执行线程B,线程B继续输出字符。由于线程A正在输出字符,线程B

  会继续输出,画面字符会产生混乱。

  相关代码:

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"


DWORD CALLBACK TestProc(LPVOID pParam)
{
    char *pszText = (char*)pParam;
    while (1)
    {
        printf("%s\n", pszText);
        Sleep(1000);
    }
    return 0;
}
DWORD CALLBACK TestProc2(LPVOID pParam)
{
    char *pszText = (char*)pParam;
    while (1)
    {
        printf("%s\n", pszText);
        Sleep(1000);
    }
    return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
    DWORD nID = 0;
    char *pszText = "************";
    HANDLE hThread = CreateThread(NULL, 0, TestProc, pszText, 0, &nID);
    char *pszText2 = "------------";
    HANDLE hThread2 = CreateThread(NULL, 0, TestProc2, pszText2, CREATE_SUSPENDED, &nID);
    getchar();
    SuspendThread(hThread);
    ResumeThread(hThread2);
    getchar();
    return 0;
}
View Code

6.线程同步技术

  原子锁、临界区、互斥(用于加锁)

  事件、信号量、可等候定时器(用于线程同步,线程之间协调工作)

  (1)等候函数

     //等候单个

     DWORD  WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );

     //等候多个

     DWORD  WaitForMultipleObjects( DOWRD              nCount,             //句柄数量

                                                              CONST HANDLE   *lpHandles,        //句柄buff地址

                                                              BOOL                  bWaitAll,            //等候方式

                                                              DWORD              dwMilliseconds);  //等候时间

     等候方式:TRUE - 所有句柄都有信号,才结束等候

          FALSE - 只要有一个有信号,就结束等候

  (2)原子锁

     a.相关问题

       多个线程同时对一个数据进行原子操作,会产生结果丢失。比如++运算时,线程A执行g_nValue++时,如果线程切换时间正好在线程A

       将值保存到g_nValue之前,线程B继续执行g_nValue++。那么,当线程A再次切换回来之后,会将原来线程A还未保存的值保存到

       g_nValue上,线程B进行的操作就被覆盖。

       b.使用

       原子锁 - 对单条指令的操作

       API:LONG InterlockedIncrement( LPLONG volatile lpAddend );

           LONG InterlockedDecrement (LPLONG volatile lpAddend );

           LONG InterlockedCompareExchange( LPLONG volatile Destination, LONG Exchange, LONG Comperand );

           LONG InterlockedExchange( LONG volatile Target, LONG Value );

           ...

     c.原子锁的实现

       直接对数据所在的内存操作,并且任何一个瞬间只能有一个线程访问。

  相关代码:

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"

DWORD g_nValue = 0;

DWORD CALLBACK TestProc1(LPVOID pParam)
{
    for (DWORD i = 0; i < 100000000; i++)
    {
        //g_nValue++;
        InterlockedIncrement(&g_nValue);//原子锁函数
    }
    return 0;
}
DWORD CALLBACK TestProc2(LPVOID pParam)
{
    for (DWORD i = 0; i < 100000000; i++)
    {
        //g_nValue++;
        InterlockedIncrement(&g_nValue);//原子锁函数
    }
    return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
    DWORD nID = 0;
    //线程在执行时无信号,执行结束有信号
    HANDLE hThread[2] = { 0 };
    hThread[0] = CreateThread(NULL, 0, TestProc1, NULL, 0, &nID);
    hThread[1] = CreateThread(NULL, 0, TestProc2, NULL, 0, &nID);
    //防止主线程结束  getchar();
    //等待所有子线程结束
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    printf("%d\n", g_nValue);
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);
    return 0;
}
View Code

  (3)临界区

     a.相关问题

       printf输出混乱,多线程情况下同时使用一段代码。临界区可以锁定一段代码,防止多个线程同时使用该段代码。

     b.使用

       初始化一个临界区

         VOID  InitializeCriticalSection( LPCRITICAL_SECTION  lpCriticalSection );

       进入临界区

         添加到被锁定的代码之前

         VOID  EnterCriticalSection( LPCRITICAL_SECTION  lpCriticalSection );

       离开临界区

         添加到被锁定的代码之后

         VOID  LeaveCriticalSection( LPCRITICAL_SECTION  lpCriticalSection );  

       删除临界区

         VOID  DeleteCriticalSection( LPCRITICAL_SECTION lpCriticalSection );

     c.临界区和原子锁的区别

       原子锁 - 锁定单条指令

       临界区 - 锁定单条或多条指令

  相关代码:

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"


CRITICAL_SECTION cs = { 0 };

DWORD CALLBACK TestProc1(LPVOID pParam)
{
    while (1)
    {
        EnterCriticalSection(&cs);  //进入临界区
        printf("********\n");
        Sleep(1000);
        LeaveCriticalSection(&cs);  //离开临界区
    }    
    return 0;
}
DWORD CALLBACK TestProc2(LPVOID pParam)
{
    while (1)
    {
        EnterCriticalSection(&cs);  //进入临界区
        printf("--------\n");
        Sleep(1000);
        LeaveCriticalSection(&cs);  //离开临界区
    }    
    return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
    //初始化临界区
    InitializeCriticalSection(&cs);
    DWORD nID = 0;
    HANDLE hThread[2] = { 0 };
    //创建子线程
    hThread[0] = CreateThread(NULL, 0, TestProc1, NULL, 0, &nID);
    hThread[1] = CreateThread(NULL, 0, TestProc2, NULL, 0, &nID);
    //等待子线程结束
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    //关闭线程句柄
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);
    //删除临界区
    DeleteCriticalSection(&cs);
    return 0;
}
View Code

  (3)互斥

     a.相关问题

       多线程下代码或资源的共享使用

     b.使用

       创建互斥

         HANDLE  CreateMutex( LPSECURITY_ATTRIBUTES  lpMutexAttributes,  //安全属性

                                                           BOOL                                bInitialOwner,        //初始的拥有者

                                                           LPCTSTR                           lpName);               //名称

         创建成功返回互斥句柄

         互斥句柄是可等候句柄(进程句柄、线程句柄是互斥句柄),当任何线程都不拥有互斥句柄时,有信号;被线程拥有时,有信号。

         出事拥有者:TRUE - 创建互斥的线程拥有互斥

               FALSE - 创建时没有线程拥有互斥

       等候互斥

         WaitForMultipleObjects

         互斥的等候遵循谁先等候谁线获取

       释放互斥

         BOOL  ReleaseMutex( HANDLE  hMutex );

       关闭互斥句柄

         CloseHandle

     c.互斥和临界区的区别

       临界区 - 用户态,自行效率高,只能在同一个进程中使用

       互斥 - 内核态,执行效率低,可以通过命名的方式跨进程使用

  相关代码:

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"


HANDLE g_hMutex = 0;  //接收互斥句柄

DWORD CALLBACK TestProc1(LPVOID pParam)
{
    while (1)
    {
        //如果g_hMutex有信号,通过阻塞同时将g_hMutex置为无信号
        //如果g_hMetex无信号,则阻塞
        WaitForSingleObject(g_hMutex, INFINITE);
        printf("********\n");
        Sleep(1000);
        ReleaseMutex(g_hMutex);
    }
    return 0;
}
DWORD CALLBACK TestProc2(LPVOID pParam)
{
    while (1)
    {
        WaitForSingleObject(g_hMutex, INFINITE);
        printf("--------\n");
        Sleep(1000);
        ReleaseMutex(g_hMutex);
    }
    return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
    //创建互斥
    g_hMutex = CreateMutex(NULL, FALSE, NULL);

    DWORD nID = 0;
    HANDLE hThread[2] = { 0 };
    hThread[0] = CreateThread(NULL, 0, TestProc1, NULL, 0, &nID);
    hThread[1] = CreateThread(NULL, 0, TestProc2, NULL, 0, &nID);
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);
    CloseHandle(g_hMutex);
    return 0;
}
View Code

  (4)事件

     a.相关问题

       程序之间的通知问题

     b.使用事件

       创建事件

         HANDLE  CreateEvent( LPSECURITY_ATTRIBUTES   lpEventAttributes,  //安全属性

                                                          BOOL                                 bManualReset,       //事件复位方式,TRUE表示手动,FALSE表示自动

                                                          BOOL                                 bInitialState,         //事件初始状态,TRUE表示有信号

                                                          LPCTSTR                            lpName);              //事件名称

         成功返回事件句柄,事件句柄是可等候句柄,有无信号,自己控制

       等候事件

         WaitForSingleObject

       触发事件

         //将事件设置成有信号状态

         BOOL  SetEvent( HANDLE  hEvent );

         //将事件设置成无信号状态

         BOOL  ResetEvent( HANDLE  hEvent );

       关闭事件

         CloseHandle

       注意:小心事件的死锁

       自动复位方式是在WaitForSingleObject( handle, INFINITE )函数中实现的

       {

         ......

         阻塞代码

         ......

         //判断handle是否为事件句柄

         if  ( 事件句柄 )

         {

           //通过事件句柄查看事件的复位方式

           if  ( 手动 ) {  //什么都不干  }

           if  ( 自动 ) {  ResetEvent( handle );  }

         }

       }

  相关代码:

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"

HANDLE g_hEvent = 0;

DWORD CALLBACK PrintProc(LPVOID pParam)
{
    while (1)
    {
        WaitForSingleObject(g_hEvent, INFINITE);
        ResetEvent(g_hEvent);  //事件复位(事件变成无信号)
        printf("********\n");        
    }
    return 0;
}
DWORD CALLBACK CtrlProc(LPVOID pParam)
{
    while (1)
    {
        Sleep(1000);
        SetEvent(g_hEvent);  //将事件设置成有信号
    }
    return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
    //创建事件,手动复位,初始无信号
    g_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    DWORD nID = 0;
    HANDLE hThread[2] = { 0 };
    hThread[0] = CreateThread(NULL, 0, PrintProc, NULL, 0, &nID);
    hThread[1] = CreateThread(NULL, 0, CtrlProc, NULL, 0, &nID);
    WaitForMultipleObjects(2, hThread, TRUE, INFINITE);
    CloseHandle(hThread[0]);
    CloseHandle(hThread[1]);
    CloseHandle(g_hEvent);
    return 0;
}
View Code

  (5)信号量

     a.相关问题

       功能类似于事件,解决通知的相关问题。但是可以提供一个计数器,可以设置次数。

     b.使用信号量

       创建信号量

         HANDLE  CreateSemaphore( LPSECURITY_ATTRIBUTES  lpSemaphoreAttributes,  //安全属性

                                                                  LONG                                lInitialCount,                 //信号量的初始计数值

                                                                  LONG                                lMaximumCount,           //信号量的最大值

                                                                  LPCTSTR                           lpName);                      //名称   

         创建成功返回信号量句柄(可等候句柄),计数值不为0有信号,为0无信号。  

       等候信号量

         WaitForSingleObject

         等候没通过一次,信号量计数值减1,知道为0阻塞

       释放信号量(重新设置信号量的计数值)

         BOOL  ReleaseSemaphore( HANDLE  hSemaphore,          //信号量句柄

                                                                LONG      lReleaseCount,       //释放数量

                                                                LPLONG   lpPreviousCount);   //原来信号量的数量,可以为NULL

       关闭句柄

         CloseHandle

  相关代码: 

#include "stdafx.h"
#include "windows.h"
#include "stdio.h"

HANDLE g_hSemaphore = 0;

DWORD CALLBACK TestProc(LPVOID pParam)
{
    while (1)
    {
        WaitForSingleObject(g_hSemaphore, INFINITE);
        printf("********\n");
    }
    return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
    g_hSemaphore = CreateSemaphore(NULL, 3, 10, NULL);
    DWORD nID = 0;
    HANDLE hThread = CreateThread(NULL, 0, TestProc, NULL, 0, &nID);
    getchar();
    //重新设置计数值,不能超过创建时的最大计数值(10)
    ReleaseSemaphore(g_hSemaphore, 10, NULL);
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    CloseHandle(g_hSemaphore);
    return 0;
}
View Code

 

posted @ 2016-07-14 18:24  甩锅侠  阅读(205)  评论(0编辑  收藏  举报