MFC——9.多线程与线程同步

Lesson9:多线程与线程同步

程序、进程和线程是操作系统的重点,在计算机编程中,多线程技术是提高程序性能的重要手段。本文主要讲解操作系统中程序、进程和线程之间的关系,并通过互斥对象和事件对象实例说明多线程和线程同步技术。

1.      程序、进程和线程

1.1  程序和进程

程序是计算机指令的集合,它以文件的形式存储在磁盘上。进程通常被定义为一个正在运行的程序的实例,是一个程序在其自身的地址空间中的一次执行活动。进程是资源申请、调度和独立运行的单位,因此,它使用系统中的运行资源;而程序不能申请系统资源,不能被系统调度,也不能作为独立运行的单位,因此,它不占用系统的运行资源。

进程由两个部分组成:

1、操作系统用来管理进程的内核对象。内核对象也是系统用来存放关于进程的统计信息的地方。

2、地址空间。它包含所有可执行模块或DLL模块的代码和数据。它还包含动态内存分配的空间。如线程堆栈和堆分配空间。

每个进程有它自己的私有地址空间。进程A可能有一个存放在它的地址空间中的数据结构,地址是0x12345678,而进程B则有一个完全不同的数据结构存放在它的地址空间中,地址是0x12345678。当进程A中运行的线程访问地址为0x12345678的内存时,这些线程访问的是进程A的数据结构。当进程B中运行的线程访问地址为0x12345678的内存时,这些线程访问的是进程B的数据结构。进程A中运行的线程不能访问进程B的地址空间中的数据结构,反之亦然。一个进程不能读取、写入、或者以任何方式访问驻留在该分区中的另一个进程的数据。对于所有应用程序来说,该分区是维护进程的大部分数据的地方。

1.2  进程和线程

进程是不活泼的。进程从来不执行任何东西,它只是线程的容器。若要使进程完成某项操作,它必须拥有一个在它的环境中运行的线程,此线程负责执行包含在进程的地址空间中的代码。单个进程可能包含若干个线程,这些线程都“同时” 执行进程地址空间中的代码。每个进程至少拥有一个线程,来执行进程的地址空间中的代码。当创建一个进程时,操作系统会自动创建这个进程的第一个线程,称为主线程。此后,该线程可以创建其他的线程。操作系统为每一个运行线程安排一定的CPU时间——时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间内运行,因时间片相当短,因此,给用户的感觉,就好像线程是同时运行的一样。如果计算机拥有多个CPU,线程就能真正意义上同时运行了。

线程由两个部分组成:

1、线程的内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。当创建线程时,系统创建一个线程内核对象。该线程内核对象不是线程本身,而是操作系统用来管理线程的较小的数据结构。可以将线程内核对象视为由关于线程的统计信息组成的一个小型数据结构。

2、  线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量。

线程总是在某个进程环境中创建。系统从进程的地址空间中分配内存,供线程的堆栈使用。新线程运行的进程环境与创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易地互相通信。线程只有一个内核对象和一个堆栈,保留的记录很少,因此所需要的内存也很少。因为线程需要的开销比进程少,因此在编程中经常采用多线程来解决编程问题,而尽量避免创建新的进程。

2.      多线程

主线程可以创建多个子线程,每个线程有自己的时间片,当时间片到了就执行下一个线程。为了让线程能严格交替执行,可以用互斥对象和事件对象实现。

2.1   互斥对象实现线程同步

//实现主线程里的新线程在交替时间片内运行
#include <windows.h>
#include <iostream>
using namespace std;

//线程函数 原型声明
DWORD WINAPI Fun1Proc(LPVOIDlpParameter);   // thread data
DWORD WINAPI Fun2Proc(LPVOIDlpParameter);   // thread data

//int index = 0;       //定义一个循环计数
int tickets = 100;
HANDLE hMutex;         //定义一个全局的互斥对象

void main()
{
   //主程序运行时,自动创建主线程,我们用CreateThread()函数创建线程

         HANDLEhThread1;
         HANDLEhThread2;
         hThread1= CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);    //(1null表示使用缺省的安全性,2 0表示和调用线程一样的大小,3指定线程的入口函数地址,4传递给线程的参数, 5 0表示一旦创建立即运行如果设置为CREATE_SUSPENDED 表示遇到 ResumeThread function 时调用,6线程的ID ,不使用用NULL )
         hThread2= CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);

         CloseHandle(hThread1);     //这里刚创建线程就关闭,其实并没有终止创建的线程。只是主线程中对新线程的引用不感兴趣,关闭后可以减小线程内核的引用计数
         CloseHandle(hThread2);   

         hMutex=CreateMutex(NULL,FALSE, NULL);         //创建互斥对象,如果false改为true,则表示主线程拥有互斥对象,所以如果主线程不释放互斥对象,别的线程是得不到互斥对象的。

         //通过命名的互斥对象,让应用程序只有一个实例运行
         /*hMutex= CreateMutex(NULL, FALSE, "tickets");
         if(hMutex)
         {
                   if(ERROR_ALREADY_EXISTS == GetLastError())
                   {
                            cout<< "only instance is running"<< endl;
                            return;
                   }
         }*/

         //如果为true,则表示拥有互斥对象,若再次请求互斥对象,互斥对象的计数器会加一,表示又请求了一次,而且请求成功
         /*hMutex= CreateMutex(NULL,TRUE, NULL);
         WaitForSingleObject(hMutex,INFINITE);        //虽然主线程拥有互斥对象,所以它现在为未通知状态,但请求互斥对象的ID 和拥有互斥对象的ID 是同一个ID ,所以可以请求到
         ReleaseMutex(hMutex);      //拥有了两次,释放两次(多次请求,多次释放。通过计数器记录的)
         ReleaseMutex(hMutex);*/

         //主线程不能在卖完100张票前结束,主线程睡眠足够时间让子线程有足够时间片执行
         Sleep(4000);
         //Sleep(10);             //暂停函数,这里暂停10ms,   sleep time in milliseconds
         system("pause");
}

//线程函数 实现
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
         while(TRUE)
         {
                   //线程1得到互斥对象,互斥对象的ID就为线程1的线程ID,互斥对象变为未通知状态,
                   WaitForSingleObject(hMutex,INFINITE);      //互斥对象相当于一个钥匙,有了它,才能往下执行,此时钥匙在我这,即使线程睡觉了,别人进不了这个房间,当离开房间,交出钥匙,别人才能拿到钥匙,进去房间

                   if(tickets > 0)
                   {
                            Sleep(1);   //执行sleep表示操作系统暂时放弃当前线程一段时间,执行下一个线程,此时这个线程停止在这里,当下次这个线程运行时,接着运行下一行
                            cout<< "Thread1 sell ticket:" << tickets-- << endl;
                   }
                   else
                            break;
                   ReleaseMutex(hMutex);    //释放互斥对象,释放后操作系统将互斥对象线程ID为0,互斥对象为已通知状态,这样线程2才能获得互斥对象
         }

         /*WaitForSingleObject(hMutex,INFINITE);//如果操作系统认为线程结束,那么在这个线程里请求的互斥对象引用计数和线程ID为零,线程2就可以得到互斥对象了
         cout<< "Thread1 is running!" <<endl;*/

         return0;
}

DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
         while(TRUE)
         {
                   WaitForSingleObject(hMutex,INFINITE);    //线程1在sleep时,执行线程2,但线程2这里发生互斥,执行不了,然后当线程1睡醒了就继续执行线程1,
                   if(tickets > 0)
                   {
                            Sleep(1);
                            cout<< "Thread2 sell ticket:" << tickets-- << endl;
                   }
                   else
                            break;
                   ReleaseMutex(hMutex);
         }

         /*WaitForSingleObject(hMutex,INFINITE);
         cout<< "Thread2 is running!" << endl;*/
         return0;
}

程序中通过互斥对象实现在每个子线程的时间片内交替循环执行一次,tickes是全局变量。互斥对象(mutex)属于内核对象,它能够确保线程拥有对单个资源的互斥访问权。互斥对象包含一个使用数量,一个线程ID和一个计数器。ID用于标识系统中的哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

下面是程序中几个关键函数

1.创建互斥对象

HANDLE CreateThread(         //The CreateThread function creates a thread to execute within theaddress space of the calling process.
          LPSECURITY_ATTRIBUTESlpThreadAttributes,  // pointer to securityattributes,结构体指针,
          DWORDdwStackSize,     // initial thread stacksize,指定初始栈大小
          LPTHREAD_START_ROUTINElpStartAddress,    // pointer to threadfunction,指向一个应用程序的线程的指针
          LPVOIDlpParameter, // argument for new thread,指定一个单独的参数值传递给线程
          DWORDdwCreationFlags, // creation flags,指定控制线程创建的附加标记
          LPDWORDlpThreadId    // pointer to receivethread ID,函数的返回值,指向一个变量,接收线程的标示符ID
          );

2.创建互斥对象

HANDLE CreateMutex(     //创建互斥对象,完成线程的同步 TheCreateMutex function creates a named or unnamed mutex object.
        LPSECURITY_ATTRIBUTES lpMutexAttributes,    // pointer to security attributes,结构指针,NULL 表示默认的安全性
        BOOL bInitialOwner,        //flag for initial ownership,互斥对象初始拥有者,真表示调用者创建互斥对象,调用的线程获得互斥对象的所有权,否则不获得
        LPCTSTR lpName     //pointer to mutex-object name),互斥对象名字,null表示没有名字
)

3.请求互斥对象

WaitForSingleObject        //下面两种情况发生时,这个函数返回
The specified object is in the signaled state.     //指定的对象处于有信号状态
The time - out interval elapses.               //超时的时间间隔流逝了

DWORD WaitForSingleObject(     //The WaitForSingleObject function returns when one of the following occurs :
       HANDLE hHandle,              // handle to object to wait for,等待的互斥对象的句柄
       DWORD dwMilliseconds         // time-out interval in milliseconds,超时的时间间隔,如果时间流逝了,即使所等待的对象处于非信号状态的,函数返回。参数为0,表示测试对象状态,立即返回,参数为INFINITE,表示一直等待,直到等待对象处于有信号状态
);

4.释放互斥对象           //那个线程拥有互斥对象,哪个线程释放互斥对象

ReleaseMutex             //释放指定互斥对象的所有权,执行成功返回非0.失败返回0
BOOL ReleaseMutex(         //The ReleaseMutex function releasesownership of the specified mutex object.
       HANDLE hMutex             // handle to mutex object
);
 

2.2   事件对象实现线程同步

#include<Windows.h>
#include<iostream>
using namespace std;

//线程函数 原型声明
DWORD WINAPI Fun1Proc(LPVOIDlpParameter);   // thread data
DWORD WINAPI Fun2Proc(LPVOIDlpParameter);   // thread data

int tickets = 100;
HANDLE g_hEvent;                            //定义一个全局的互斥对象的句柄,保存时间对象的句柄
void main()
{
         HANDLEhThread1;
         HANDLEhThread2;
         hThread1= CreateThread(NULL, 0, Fun1Proc, NULL, 0, NULL);    //(1null表示使用缺省的安全性,2 0表示和调用线程一样的大小,3指定线程的入口函数地址,4传递给线程的参数, 5 0表示一旦创建立即运行如果设置为CREATE_SUSPENDED 表示遇到 ResumeThread function 时调用,6线程的ID ,不使用用NULL )
         hThread2= CreateThread(NULL, 0, Fun2Proc, NULL, 0, NULL);

         CloseHandle(hThread1);    //这里刚创建线程就关闭,其实并没有终止创建的线程。只是主线程中对新线程的引用不感兴趣,关闭后可以减小线程内核的引用计数
         CloseHandle(hThread2);

/*  HANDLE CreateEvent(The CreateEvent function creates a named or unnamedevent object.
                   LPSECURITY_ATTRIBUTESlpEventAttributes,     // pointer tosecurity attributes   NULL表示默认安全性
                   BOOLbManualReset,      // flag for manual-reset event,指定是否人工重置或自动重置的对象被创建。真表示人工重置,假表示自动重置
                   BOOLbInitialState,                          // flag for initial state,指定事件初始化状态
                   LPCTSTRlpName                               // pointer to event-object name,事件对象的名字
                   );*/

         //g_hEvent= CreateEvent(NULL,FALSE,FALSE,NULL);             //第三个参数指定初始化为无信号状态
         g_hEvent= CreateEvent(NULL, FALSE, FALSE, "tickets");       //创建命名的互斥对象
         if(g_hEvent)
         {
                   if(ERROR_ALIAS_EXISTS==GetLastError())
                   {
                            cout<< "only instance can run!" << endl;
                            return;
                   }
         }

         SetEvent(g_hEvent);     //设定为有信号状态,人工重置的对象为有信号状态时,所以等待该时间的线程,都可以调度,可以同时运行。
                              //自动重置的对象,为有信号状态时,当线程得到该对象,操作系统自动设置为无信号状态,这样别的线程无法得到此对象。
         Sleep(4000);
         CloseHandle(g_hEvent);
}

//线程函数 实现
DWORD WINAPI Fun1Proc(LPVOID lpParameter)
{
         while(TRUE)
         {
                   //线程1得到互斥对象,互斥对象的ID就为线程1的线程ID,互斥对象变为未通知状态,
                   WaitForSingleObject(g_hEvent,INFINITE);      //互斥对象相当于一个钥匙,有了它,才能往下执行,此时钥匙在我这,即使线程睡觉了,别人进不了这个房间,当离开房间,交出钥匙,别人才能拿到钥匙,进去房间

                   if(tickets > 0)
                   {
                            Sleep(1);     //执行sleep表示操作系统暂时放弃当前线程一段时间,执行下一个线程,此时这个线程停止在这里,当下次这个线程运行时,接着运行下一行
                            cout<< "Thread1 sell ticket:" << tickets-- << endl;
                   }
                   else
                            break;
                   SetEvent(g_hEvent);        //将事件对象设置为有信号状态,释放互斥对象的控制权,不再运行此线程,这个线程释放了互斥对象的控制权后,如果其他进程在等待互斥对象置位,则等待的线程可以得到该互斥对象,等待函数返回,互斥对象被新的线程所拥有。
         }
         return0;
}

DWORD WINAPI Fun2Proc(LPVOID lpParameter)
{
         while(TRUE)
         {
                   WaitForSingleObject(g_hEvent,INFINITE);    //线程1在sleep时,执行线程2,但线程2这里发生互斥,执行不了,然后当线程1睡醒了就继续执行线程1

                   if(tickets > 0)
                   {
                            Sleep(1);
                            cout<< "Thread2 sell ticket:" << tickets-- << endl;
                   }
                   else
                            break;
                   SetEvent(g_hEvent);
         }
         return0;
}

事件对象也属于内核对象,包含一个使用计数,一个用于指明该事件是一个自动重置的事件还是一个人工重置的事件的布尔值,另一个用于指明该事件处于已通知状态还是未通知状态的布尔值。有两种不同类型的事件对象。一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。

互斥对象和事件对象属于内核对象,利用内核对象进行线程同步,速度较慢,但利用互斥对象和事件对象这样的内核对象,可以在多个进程中的各个线程间进行同步。

时间: 2024-10-05 11:13:24

MFC——9.多线程与线程同步的相关文章

mfc小工具开发之定时闹钟之---多线程急线程同步

一.MFC对多线程编程的支持 MFC中有两类线程,分别称之为工作者线程和用户界面线程.二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环. 工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等.用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等.但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务. 在MFC中,一般用全局函数Afx

java多线程之 ---- 线程同步

java多线程之线程同步 线程同步 定义:同步是指在同一时间段内只能运行一个线程. 分类:同步方法.同步块. 作用:安全解决共享问题. 同步块: 语法: synchronized (同步对象) { 需要同步的代码; } 例子: public class ThreadDemo implements Runnable{ private int ticket = 5; public void run(){ for(int i=1;i<=5;i++){ synchronized (this){ if(t

Linux程序设计学习笔记----多线程编程线程同步机制之互斥量(锁)与读写锁

互斥锁通信机制 基本原理 互斥锁以排他方式防止共享数据被并发访问,互斥锁是一个二元变量,状态为开(0)和关(1),将某个共享资源与某个互斥锁逻辑上绑定之后,对该资源的访问操作如下: (1)在访问该资源之前需要首先申请互斥锁,如果锁处于开状态,则申请得到锁并立即上锁(关),防止其他进程访问资源,如果锁处于关,则默认阻塞等待. (2)只有锁定该互斥锁的进程才能释放该互斥锁. 互斥量类型声明为pthread_mutex_t数据类型,在<bits/pthreadtypes.h>中有具体的定义. 互斥量

C#多线程之线程同步3

在上一篇C#多线程之线程同步2中,我们主要学习了AutoResetEvent构造.ManualResetEventSlim构造和CountdownEvent构造,在这一篇中,我们将学习Barrier构造.ReaderWriterLockSlim构造和SpinWait构造. 七.使用Barrier构造 在这一小节中,我们将学习一个比较有意思的同步构造:Barrier.Barrier构造可以帮助我们控制多个等待线程达到指定数量后,才发送通知信号,然后所有等待线程才能继续执行,并且在每次等待线程达到指

关于Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文)

Java多线程的线程同步和线程通信的一些小问题(顺便分享几篇质量高的博文) 前言:在学习多线程时,遇到了一些问题,这里我将这些问题都分享出来,同时也分享了几篇其他博客主的博客,并且将我个人的理解也分享给大家. 一.对于线程同步和同步锁的理解(注:分享了三篇高质量的博客) 以下我精心的挑选了几篇博文,分别是关于对线程同步的理解和如何选择线程锁以及了解线程锁的作用范围. <一>线程同步锁的选择 1. 这里我推荐下Java代码质量改进之:同步对象的选择这篇博文. 2. 以上推荐的博文是以卖火车票为例

系统API函数实现多线程及线程同步

1.线程的创建 须包含头文件:#include <windows.h> HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId ); lpThreadAttributes:指向SECURI

C# 多线程(二) 线程同步基础

本系列的第一篇简单介绍了线程的概念以及对线程的一些简单的操作,从这一篇开始讲解线程同步,线程同步是多线程技术的难点.线程同步基础由以下几个部分内容组成 1.同步要领(Synchronization Essentials) 2.锁(Locking) 3.线程安全(Thread Safety) 4.事件等待句柄(Signaling with Event Wait Handles) 5.同步上下文(Synchronization Contexts) 同步要领(Synchronization Essen

多线程之线程同步

多线程内容大致分两部分,其一是异步操作,可通过专用,线程池,Task,Parallel,PLINQ等,而这里又涉及工作线程与IO线程:其二是线程同步问题,鄙人现在学习与探究的是线程同步问题. 通过学习<CLR via C#>里面的内容,对线程同步形成了脉络较清晰的体系结构,在多线程中实现线程同步的是线程同步构造,这个构造分两大类,一个是基元构造,一个是混合构造.所谓基元则是在代码中使用最简单的构造.基元构造又分成两类,一个是用户模式,另一个是内核模式.而混合构造则是在内部会使用基元构造的用户模

C# 多线程(二) 线程同步基础(上)

本系列的第一篇简单介绍了线程的概念以及对线程的一些简单的操作,从这一篇开始讲解线程同步,线程同步是多线程技术的难点.线程同步基础由以下几个部分内容组成 1.同步要领(Synchronization Essentials) 2.锁(Locking) 3.线程安全(Thread Safety) 4.事件等待句柄(Signaling with Event Wait Handles) 5.同步上下文(Synchronization Contexts) 同步要领(Synchronization Essen