c++下使用线程API函数版(基本概念以及同步)

by admin on 2020年1月27日

    有过多线程开发经历的人,对于“互斥对象,临界区,事件,信号量”这四个对象肯定不会陌生。就我自己而言,它们真是”让我欢喜让我忧”。”让我欢喜”
因为利用它们往往能解决掉非常棘手的问题,例如经典的”生产者-消费者”问题,”让我忧”因为,我经常把它们搞混淆,用的时候不知道到底该用哪个好,比如我觉互斥对象和信号量这两者很相似,在解决”生产者-消费者”问题时,为了实现线程同步很多例子中都用了信号量,为什么不用互斥对象实现呢?我试图用互斥对象来做了一下,结果失败了。

  
关于线程的基本概念,博友可参考我的c#Thread文章.本文所讨论的是在c++下使用线程.

临界区的互斥控制

 

  
本文只是线程的一个浓缩版,目的是可以让大家能快速的熟悉在c++是使用线程,希望大家踊跃拍砖.

一、前言
我正在研究线程的通讯,无奈有关这方面的资料实在太少,没办法我只好去啃MSDN,但是MSDN好像说得也不太清楚。所以那我就写了这么一个例子,以望对学习多线程编程起到引玉抛砖的作用。有个易懂的例子学起来总是容易很多。近来我正在复习那几个排序算法,于是就把这些算法写到了这里来作为线程的例子。同时也对几个通用的排序算法思想作了一些说明。

   
今天我决定解决掉这个问题。仔细阅读了MSDN上关于”Mutex,CriticalSection,Envent,Semaphore”的相关函数的介绍,特被是Create*
函数的介绍后,我终于弄清楚了这个问题;

  用API函数CreateThread可创建线程.该函数原型为

这个例子利用多线程使用不同的排序算法对数据进行排序,每一个线程使用不同的算法。主线程里使用快速排序QuickSort,其他四个算法分别建立四个子线程,在子线程中进行排序。因为每一个线程都要调用函数PrintResult把结果输出到显示器上,所以不同的线程就会争夺着向显示器输出,这样,不同线程的输出就会混合在一起,所以呢必须让线程一个接着一个输出。也就是必须对PrintResult进行互斥控
制。要进行互斥控制,则必须用到Event、Mutex、CrititicalSection、Semaphore等互斥控制量。这个例子可以使用Event、Mutex、CrititicalSection,你可以根据提示修改代码使用其中的一种互斥量进行测试。
我所写的例子没有使用MFC,用的都是SDK的WINAPI,如果使用MFC时有些许差别,但原理是一样的。而且MFC还把线程分成用户界面线程和工作者线程,实质上用户界面线程跟工作者线程的差别是,用户界面线程要继承的基类已经实现了消息循环,MFC帮你做了很多的消息处理和界面控制的工作。

    首先我给它们分成两组:

 

一、WINAPI线程控制函数简介:有关详细说明请查看MSDN

    Mutex和CriticalSection是用于实现数据的互斥的访问;

  HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes,
    DWORN dwStackSize,
    LPTHREAD_START_ROUTINE lpStartAddress, 
    LPVOID lpParameter,
    DWORD dwCreationFlags, 
    LPDWORD lpThreadId
)

1.1 线程建立函数 HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
// 安全属性结构指针,可为NULL
DWORD dwStackSize,
// 线程栈大小,若为0表示使用默认值
LPTHREAD_START_ROUTINE lpStartAddress,
// 指向线程函数的指针
LPVOID lpParameter,
// 传递给线程函数的参数,可以保存一个指针值
//所以,线程函数的参数只能是一个32位值
//而且线程函数返回值也有规定,必须是unsigned long
DWORD dwCreationFlags,
// 线程建立是的初始标记,运行或挂起
LPDWORD lpThreadId
// 指向接收线程号的DWORD变量
);

    Envent和Semaphore不但可以用于实现互斥,而且能够能与实现线程同步;

 

1.2 临界资源控制函数:

 

该函数参数较多,不过不用急,我们一步一步来分析参数.
1:lpThreadAttributes
这个参数是指向SECURITY_ATTRIBUTES结构的指针.SECURITY_ATTRIBUTES结构主要主用是设置线程安全性的,我们在用

1)事件对象的创建
事件对象的作用是为线程传送一个公共的事件信号,使用CreateEvent函数创建:

    究竟是什么原因导致Mutex不能实现线程同步呢?

CreateThread创建线程的时候可用NULL给它赋值(让创建的线程使用默认的安全性).

HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
// 安全属性结构指针,可为NULL
BOOL bManualReset,
// 手动清除信号标记,TRUE在WaitForSingleObject后必须手动调
//用RetEvent清除信号。
//若为FALSE则在WaitForSingleObject后,系统自动清除事件信号
BOOL bInitialState, // 初始状态,TRUE有信号,FALSE无信号
LPCTSTR lpName // 信号量的名称,字符数不可多于MAX_PATH
//如果遇到同名的其他信号量函数就会失败,如果遇到同类信号同名
//也要注意变化
);
2)互斥量的创建
互斥量的作用是保证每次只能有一个线程获得互斥量而得以继续执行,使用CreateMutex函数创建:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes,
// 安全属性结构指针,可为NULL
BOOL bInitialOwner, // 当前建立互斥量是否占有该互斥量
//TRUE表示占有,这样其他线程就不能获得此互斥量也就无法进入由
//该互斥量控制的临界区。FALSE表示不占有该互斥量
LPCTSTR lpName // 信号量的名称,字符数不可多于MAX_PATH
//如果遇到同名的其他信号量函数就会失败,如果遇到同类信号同名
//也要注意变化
);
3)临界区信号的初始化
使用前必须先初始化
VOID InitializeCriticalSection(
LPCRITICAL_SECTION lpCriticalSection // 临界区变量指针
);
4)阻塞函数
如果等待的信号量不可用,那么线程就会挂起,直到信号可用线程才会被唤醒,该函数会自动修改信号,如Event,线程被唤醒之后
Event信号会变得无信号,Mutex、Semaphore等也会变。我们使用WaitForSingleObject函数等待信号,如果要等待多个信号可以使用WaitForMutipleObject函数。
DWORD WaitForSingleObject(
HANDLE hHandle, // 等待对象的句柄
DWORD dwMilliseconds // 等待毫秒数,INFINITE表示无限等待
);
二、实例讲解
下面我们结合本文的示例代码进行具体的讲解:

   
答案:Mutex在没有被任何线程所拥有时是有信号的,这时任何线程可以使用Wait
functions去获取该对象的所有权。当Mutex被某个线程拥有后就处于没信号状态,并且只有当该线程使用ReleaseMutex释放该互斥对象时,它才能被其它线程获得。

2:dwStackSize 
这个参数的主要是设置创建的线程栈的大小.这里出现了一个技术词(栈).
栈是数据在内存中的一个位置。
提到栈我们就顺便提下数据在内存中的存放形式。
一般有如下形式
 栈:由编译器自动分配并且释放
 堆:一般由程序员手动释放.
 寄存器:一般存放全局变量和静态变量等
 
 对于int a这样的声明,自动自动为a在栈中开辟空间,而 person *p=new
person这样的声明来说
变量p就是存放在堆中,并且需要手动释放入delete
p;在c#或java这样的语言来说回收由系统自动回收.
一般我们不需要手动释放.
在这里dwStackSize意义是设置线程dwStackSize的大小.如果这里设为0就默认使用和条用线程的函数一样大。

2.1 函数、变量的申明
#include “stdafx.h”
#include “stdlib.h”
#include “memory.h”
HANDLE evtTerminate; //事件信号,标记是否所有子线程都执行完
下面使用了三种控制方法,你可以注释其中两种,使用其中一种。注意修改时要连带修改临界区PrintResult里的相应控制语句
HANDLE evtPrint; //事件信号,标记事件是否已发生
//CRITICAL_SECTION csPrint; //临界区
//HANDLE mtxPrint;
//互斥信号,如有信号表明已经有线程进入临界区并拥有此信号
static long ThreadCompleted = 0;
/*ThreadCompleted用来标记四个子线程中已完成线程的个数,当一个子线程完成时

    
在一个线程中试图释放另个线程所拥有的Mutex是不会成功的,Mutex只能被所有者线程所释放。(这是主要原因)

3:lpStartAddress
指向LPTHREAD_START_ROUTINE类型的函数指针.是线程的起始地址

 

4:pParameter 命令行参数 可为空

   我们看看”生产者-消费者”问题:

5:dwCreationFlags
创建线程的附加标记.它可以是两个值中的一个可以是CREATE_SUSPENDED或者0,如果是CREATE_SUSPENDED,线程创建后处理

   全局共享变量g_share相当于一个货架(一个只能摆放一件产品的货架)。初始情况下货架为空。只有当货架不为空时才允许消费者(DecProc)从货架上取走商品(减一操所),只有当货架上为空时才允许生产者(AddProc)往货架上放商品。生产者与消费者必须实现同步。否则就会出现不合逻辑的现象。

暂停状态,为0则理解执行

            
图片 1

6:lpThreadId 这个参数是一个返回值,用来接收线程ID。。

从上图我们可以看出在AddProc中需要对g_decSemaphore进行释放操作,在DecProc中需要对g_addSemaphore进行释放操作。但是由于Mutex的释放操作只能由所有者线程进程,在一个线程中释放其它线程所拥有的Mutex是不会成功的。正是这个原因,导致Mutex不能用于实现线程同步。而Envent,Semaphore和都可以在其它线程中设置其信号状态,所以他们能够用于实现线程同步。

我们大致对CreateThread函数有所了解后,下面我们用一个简单的程序来说明下怎么使用线程

使用信号量Semaphore进行线程同步的例子很多,下面我给出一个用Envent实现同步的例子:

我们用VC6创建一个空的控制台程序

 

 

 #include
<windows.h>

 

int
g_share=0;

#include <windows.h>
#include <iostream.h>

HANDLE
hmutex;

//首先声明下线程函数地址
DWORD WINAPI FunOne(
    LPVOID lpParameter
);

HANDLE
g_addEvent;

void main()
{
    HANDLE hThread1;
    hThread1=CreateThread(NULL,0,FunOne,NULL,0,NULL);
    CloseHandle(hThread1);//关闭打开的线程句柄
    /*
    Sleep(1);
    这里如果不添加Sleep函数就会看见输出Response FunOne
    至于为什么请大家思考思考
    */
    cout<<“Response main”<<endl;
}

HANDLE
g_decEvent;

DWORD WINAPI FunOne(
    LPVOID lpParameter
)
{
    cout<<“Response FunOne “<<endl;
    return 0;
};

DWORD
WINAPI AddProc(

 

  LPVOID lpParameter   // thread data

 

  );

 

DWORD
WINAPI DecProc(

线程同步

  LPVOID lpParameter   // thread data

  
关于同步的概念在此我们也不讲解呢,由不太明白的博客可参考我的c#系列文章。在这里我将定义一个全局变量,用两个创建的线程来同事访

  );

问该变量.

int
main()

   这里我们用到一个互斥对象mutex.
  
我们可以用CreateMutex函数来创建互斥对象.互斥对象包含一个线程ID和一个计数器.ID标识当前线程的ID,计算器标明该线程使用互斥对象

{

的次数.
CreateMutex的函数原型入下
 HANDLE CreateMutex(
   LPSECURITY_ATTRIBUTES lpMutexAttributes,
   BOOL bInitialOwer,
   LPCTSTR lpName
)

    SECURITY_ATTRIBUTES sa;

1:lpMutexAttributes 安全性 和CreateThread第一个参数相同
2:bInitialOwer BOOL类型
为true的话则创建者线程拥有使用互斥对象的拥有权,为false则不活的创建对象的所有权
3:lpName 互斥对象的名称

    DWORD dwStackSize=0;

ReleaseMutex
释放互斥对象的拥有权.和WaitForSingleObject函数成对使WaitForSingleObject函数的功能是请求互斥对象的拥有权..

    LPVOID lpParameter=NULL;

 

    DWORD dwCreationFlags=0;

 

    DWORD addThreadId;

下面是Demo

    DWORD decThreadId;

#include <windows.h>
#include <iostream.h>

    HANDLE addhandle;

//首先声明下线程函数地址
DWORD WINAPI FunOne(
    LPVOID lpParameter
);

    HANDLE dechandle;

DWORD WINAPI FunTwo(
    LPVOID lpParameter
);

 

int TxtNum=100;
HANDLE hMutex;
void main()
{
    HANDLE hThread1;
    HANDLE hThread2;
    hThread1=CreateThread(NULL,0,FunOne,NULL,0,NULL);
    hThread2=CreateThread(NULL,0,FunTwo,NULL,0,NULL);
    CloseHandle(hThread1);//关闭打开的线程句柄
    CloseHandle(hThread2);
    hMutex=CreateMutex(NULL,FALSE,NULL);
    Sleep(5000);
}
DWORD WINAPI FunOne(
    LPVOID lpParameter
)
{
    for(int i=1;i<100;i++)
    {
        //INFINITE参数表示WaitForSingObject用该等待直到获得所有权
        WaitForSingleObject(hMutex,INFINITE);
        if(TxtNum>0)
        {
            cout<<“妈妈正在做第”<<TxtNum–<<“道菜”<<endl;
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}

    memset((BYTE
*)&sa,0,sizeof(SECURITY_ATTRIBUTES));

DWORD WINAPI FunTwo(
    LPVOID lpParameter
)
{
    for(int i=1;i<100;i++)
    {
        //INFINITE参数表示WaitForSingObject用该等待直到获得所有权
        WaitForSingleObject(hMutex,INFINITE);
        if(TxtNum>0)
        {
            cout<<“妈妈正在做第”<<TxtNum–<<“道菜”<<endl;
        }
        ReleaseMutex(hMutex);
    }
    return 0;
}

   
sa.nLength=sizeof(SECURITY_ATTRIBUTES);

本片文章篇幅不长,欢迎交流

  

 

//创建互斥对象实现g_share的互斥访问

   
hmutex=CreateMutex(&sa,FALSE,NULL); 

   //创建事件实现进程同步

   
g_addEvent=CreateEvent(&sa,FALSE,TRUE,NULL);

   
g_decEvent=CreateEvent(&sa,FALSE,FALSE,NULL);

    addhandle=CreateThread(

              &sa,

              dwStackSize,

              AddProc,

              lpParameter,

              dwCreationFlags,

              &addThreadId);

   // Sleep(20);

    dechandle=CreateThread(

              &sa,

              dwStackSize,

              DecProc,

              lpParameter,

              dwCreationFlags,

              &decThreadId);

   
WaitForSingleObject(addhandle,INFINITE);

   
WaitForSingleObject(dechandle,INFINITE);

    CloseHandle(addhandle);

    CloseHandle(dechandle);

    return 0;

}

 

DWORD
WINAPI AddProc(

  LPVOID lpParameter   // thread data

  )

{

    int loop=200;

    while(loop>0)

    {

       
WaitForSingleObject(g_addEvent,INFINITE);

       loop-=1;

      
WaitForSingleObject(hmutex,INFINITE);

       g_share+=1;

       printf(“add proc loop: %d 
g_share:%dn”,loop,g_share);

       Sleep(10);

        ReleaseMutex(hmutex);

       SetEvent(g_decEvent);

    }

    return 0;

};

 

DWORD
WINAPI DecProc(

  LPVOID lpParameter   // thread data

  )

{

 

    int loop=200;

    while(loop>0)

    {

      
WaitForSingleObject(g_decEvent,INFINITE);

       loop-=1;

      
WaitForSingleObject(hmutex,INFINITE);

       g_share-=1;

       printf(“dec proc loop: %d 
g_share:%dn”,loop,g_share);

       Sleep(10);

       ReleaseMutex(hmutex);

       SetEvent(g_addEvent);

    }

    return 0;

};

 

             
图片 2

本博客于即日起(2009.2.26)停止更新,

新博客地址:http://www.redicecn.cn ,

本博客的大部分文章已经转移到新博客中…

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图