• 多线程


    1.进程和线程有什么区别?

    a.进程是资源分配的基本单位,线程是cpu调度,或者说是程序执行的最小单位。在Mac、Windows NT等采用微内核结构的操作系统中,进程的功能发生了变化:它只是资源分配的单位,而不再是调度运行的单位。在微内核系统中,真正调度运行的基本单位是线程。因此,实现并发功能的单位是线程。

    b.进程有独立的地址空间,比如在linux下面启动一个新的进程,系统必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据 段,这是一种非常昂贵的多任务工作方式。而运行一个进程中的线程,它们之间共享大部分数据,使用相同的地址空间,因此启动一个线程,切换一个线程远比进程操作要快,花费也要小得多。当然,线程是拥有自己的局部变量和堆栈(注意不是堆)的,比如在windows中用_beginthreadex 创建一个新 进程就会在调用CreateThread的同时申请一个专属于线程的数据块(_tiddata)。

    c.线程之间的通信比较方便。统一进程下的线程共享数据(比如全局变量,静态变量),通过这些数据来通信不仅快捷而且方便,当然如何处理好这些访问 的同步与互斥正是编写多线程程序的难点。而进程之间的通信只能通过进程通信的方式进行。

    d.由b,可以轻易地得到结论:多进程比多线程程序要健壮。一个线程死掉整个进程就死掉了,但是在保护模式下,一个进程死掉对另一个进程没有直接影响。

    e.线程的执行与进程是有区别的。每个独立的线程有有自己的一个程序入口,顺序执行序列和程序的出口,但是线程不能独立执行,必须依附与程序之中, 由应用程序提供多个线程的并发控制。

    总结:线程就是程序的一次执行,而线程可以理解为进程中执行的一段程序片段,线程是操作系统分配处理器时间的最基本单位。

    1.进程就是应用程序的运行实例,它是独立的,每个进程都有自己私有的虚拟地址空间。每个进程都有一个主线程,但可以建立另外的线程。线程运行在进程空间内。

    2.一般来说,进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程内,所以同一进程所产生的线程共享一内存空间。

    3.同一进程中的两段代码不能够同时执行,除非引入线程

    4.线程是属于进程的,当进程退出时,该进程所产生的线程都会被强制退出并清除

    5.线程占用的资源要少于进程所占用的资源,进程和线程都可以有优先级

    线程总是在某个进程环境中创建的。系统从进程的地址空间中分配内存,供线程的栈使用。新线程运行的进程环境与创建线程的环境相同。因此,新线程可以访问进程的内核对象的所有句柄、进程中的所有内存和在这个相同的进程中的所有其他线程的堆栈。这使得单个进程中的多个线程确实能够非常容易地互相通信。

    操作系统为每一个运行线程安排一定的CPU时间-时间片。系统通过一种循环的方式为线程提供时间片,线程在自己的时间片内运行,因时间片相当短,因此给用户的感觉就好像多个线程是同时运行的一样。

    例如,MFC程序中有耗时操作时,用线程可以更好的解决。一个负责耗时,另一个负责响应用户和系统消息,各司其职。

    与线程有关的常用API函数

    CreateThread          创建一个新的线程,返回已建线程的句柄

    SuspendThread         用于挂起指定线程,终止线程的执行

    ResumeThread          将线程的挂起状态计数值减1

    ExitThread            由线程自身完成终止执行操作

    TerminateThread        强行终止某一线程的操作

    GetThreadPriority        获取一个线程的优先级

    SetThreadPriority        设定一个线程在所在进程中的相对优先级

    PostThreadMessage       用于向线程发送消息,即将消息放入到指定的消息队列中,并立即返回

    1.创建线程函数:

    HANDLE CreateThread(LPSECURITY_ATTRIBUTE lpThreadAttributes,  //指向一个SECURITY_ATTRIBUTES结构的指针,该结构决定该线程的安全属性,一般置为NULL。

      DWORD dwStackSize,  //指定了线程的堆栈深度,一般都设置为0。表示与进程主线程堆栈相同

      LPTHREAD_START_ROUTINE lpStartAddress,  //新线程开始执行时代码所在的函数的地址,即线程起始地址。一般情况为(LPTHREAD_START_ROUTINE)ThreadFunc; 

      LPVOID lpParameter,  //指定了线程执行时传递给线程的32为参数,即线程函数的参数;

      DWORD dwCreationFlags, //控制线程创建时附加标志,可以取两种值.0,创建后立即开始;CREATE_SUSPENDED--线程产生后立即处于挂起状态,并不马上执行,

                    //直到函数  ResumeThread被调用。

    LPDWORD lpThreadId);    //该参数返回所创建线程的ID;如果创建成功则返回线程的句柄,否则返回NULL;

    实例:

    #include<windows.h>

    #include<iostream.h>

    DWORD WINAPI Fun1Proc(LPVOID lpParameter);

    void main()

    {

      HANDLE hThread1;

      hThread1 = CreateThread(NULL,0,Fun1Proc,NULL,0,NULL);

      CloseHandle(hThread1);

      cout<<"main thread is running"<<endl;

    }

    DWORD WINAPI Fun1Proc(LPVOID lpParameter)

    {

      cout<<"thread1 is running"<<endl

      return 0;

    }

    在创建线程完成之后,调用CloseHandle函数关闭新线程的句柄。为什么刚刚创建的线程,又将它关闭呢?原因:实际上调用CloseHandle函数并没有中止新创建的线程,只是表示在主线程中对新创建的线程的引用不感兴趣,因此将它关闭。另一方面,当关闭该句柄时,系统会递减该线程内核对象的使用计数。当创建的这个新线程执行完毕之后,系统也会递减该线程内核对象的使用次数。当使用计数为0时,系统就会释放该线程内核对象。如果没有关闭线程句柄,系统就会一直保持着对线程内核对象的引用,这样,即使该线程执行完毕,它的引用计数仍不会为0,这样该线程内核对象也就不会释放,只有等到进程终止时,系统才会清理这些残留的对象。因此,在程序中,当不再需要线程句柄时,应将其关闭,让这个线程内核对象的引用计数减1。

    该函数在调用进程的进程空间里创建一个新的线程,并返回所建线程的句柄

    typedef struct student

    {

        CString name;

        int age;

    }student;

    HANDLE myThread;    //为预先定义的HANDLE类型的句柄,存储创建线程句柄;

    DWORD ThreadID;                      //返回线程的ID

    DWORD WINAPI threadfun(LPVOID studentparameter)    //线程入口函数

    {

    }

    student student1;          //&student1为该入口函数的参数地址

    myThread = CreateThread(NULL,0,threadfun,&student1,0,&ThreadID);  //创建线程,threadfun这定义的线程入口函数

    2.线程的暂停与终止

    SuspendThread、ResumeThread、ExitThread和TerminateThread函数均能够实现停止一个线程的进行,但它们的含义不同.

    SuspendThread函数用于挂起指定线程,当线程被挂起时,也就停止了运行。它只有一个参数hThread,表示被挂起线程的句柄。每个线程都有一个挂起状态计数值,当其值大于0时,表示线程被挂起;当计数值为0时,线程就处于正常运行状态。每次调用SuspendThread函数,由hThread标识的线程的挂起状态计数值加1.如果函数执行成功,函数将返回hThread句柄标识的线程的挂起状态计数值。而ResumeThread函数将hThread句柄标识的线程的挂起状态计数值减1,如果执行成功,返回值同SuspendThread.

    ExitThread函数用于线程终止自身的执行,主要在线程的执行函数中被调用。VOID ExitThread(DWORD dwExitCode)

    函数运行后,将线程的退出码设置为dwExitCode后,终止线程的执行。其中,dwExitCode可以为用户设置的任意值,执行函数后,线程的退出码由"STILL_ACTIVE"变成dwExitCode.通过函数GetExitCodeThread可以获取线程的退出码

    调用ExitThread函数后,并不能确定从系统中清除指定的线程对象,只有该线程对象的最后一个句柄被关闭后,线程对象才被彻底清除

    TerminateThread函数可以强行终止某一线程的执行。BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode)

    其中参数hThread为被终止线程的句柄,dwExitCode用于指定线程的退出码,当函数执行成功后,返回非0值,否则返回0

    使用TerminateThread函数虽然可以立即终止线程的执行,但不释放线程所占用的资源,例如,为线程初始分配的堆栈等。因此,使用该函数来终止某个线程的执行是不安全的,可能引起系统的不稳定。

    3.线程的优先级

    系统根据每个线程的优先级进行排序,高的优先级可以使一个线程获得更多的CPU时间片,更快地完成它的任务。一个线程被创建后,就会有一个默认的优先级,可以通过GetThreadPriority函数获得线程的优先级别,而使用SetThreadPriority函数可以设置线程优先级。

    BOOL SetThreadPriority(HANDLE hThread,int nPriority)

    hThread为要设定相对优先级线程的句柄,nPriority为线程相对优先级常数,按照线程的优先级由低到高的顺序,其值可能如下

    THREAD_PRIORITY_ABOVE_NORMAL      比一般优先级高一个等级
    THREAD_PRIORITY_BELOW_NORMAL     比一般低一个等级
    THREAD_PRIORITY_HIGHEST                   比一般高2个等级(最高)
    THREAD_PRIORITY_IDLE                           空闲
    THREAD_PRIORITY_LOWEST                    比一般低2个等级(最低)
    THREAD_PRIORITY_NORMAL                    一般等级
    THREAD_PRIORITY_TIME_CRITICAL        实时

    int GetThreadPriority(HANDLE hThread)

    hThread为线程的句柄。如果函数执行成功,会返回线程的优先级标志

    向线程发送消息

    PostThreadMessage函数用于向线程发送信息,即将信息放入到指定线程的消息队列中,并立即返回

    BOOL PostThreadMessage(DWORD idThread,UINT Msg,WPARAM wParam,LPARAM lParam)

    idThread:将接收消息的线程的ID

    Msg:指定用来发送的消息

    wParam:同消息有关的字参数

    lParam:同消息有关的长参数

    该函数将一条消息放入到指定线程的消息队列中,并且不等到消息被该线程处理时便返回,调用该函数时,如果即将接收消息的线程没有创建消息循环,则该函数执行失败。

    二、MFC对多线程的支持

    在MFC中,线程分用户界面线程和工作者线程两种。用户界面线程拥有自己的消息队列和消息循环来处理界面消息,可以与用户进行交互。工作者线程没有消息循环,一般用来完成后台工作。

    在MFC中提供了对线程功能的封装类:CWinThread.MFC程序常使用的CWinApp类就是从这个类派生的。在多数情况下,程序不需要自己创建CWinThread对象。调用AfxBeginThread函数会自动创建一个CWinThread对象。

    用户界面线程的创建

    创建用户界面线程,首先需要创建一个CWinThread的派生类。过程如下:

    1.从CWinThread中派生新类

    2.重载CWinThread::InitInstance函数,在其中创建用户界面窗口,并将窗口指针赋给m_pMainWnd.

    3.如果需要,可以重载CWinThread::ExitInstance,在窗口被销毁时该函数将会被调用

    class CGUIThread:public CWinThread

    {

      public:

        CGUIThread();

        virtual BOOL InitInstance(void);

        virtual int ExitInstance(void);

    };

    CGUIThread::CGUIThread()

    {

      m_bAutoDelete = TRUE;

    }

    BOOL CGUIThread::InitInstance(void)

    {

      CWnd* pWnd = new CWnd();

      pWnd->CreateEx(0,AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW),"用户界面窗口",WS_OVERLAPPEDWINDOW|WS_VISIBLE,CRect(0,0,100,100),NULL,0);

      m_pMainWnd = pWnd;

      return(m_pMainWnd != FALSE);

    }

     int CGUIThread::ExitInstance(void)

    {

      TRACE("GUI Thread Exit ");

      return CWinThread::ExitInstance();

    }

    用户界面线程的启动则有两种方法

    1.使用全局函数AfxBeginThread,用户界面线程的AfxBeginThread函数的原型如下:

    CWinThread* AFXAPI AfxBeginThread(CRuntimeClass* pThreadClass, //指向运行时对象,CWinThread派生类对象的指针

                        int nPriority,    //优先级变量,如果为0,刚新线程和创建线程具有同样的优先级

                        UINT nStackSize, //栈尺寸,以字节为单位定义了新线程的堆栈大小,如果为0,则与创建该线程的线程相同

                        DWORD dwCreateFlags, //创建标记,控制线程创建的标志,如果是0,线程创建后立刻开始执行;如果为CREATE_SUSPENDED,线程

                                      创建后立即被挂起,挂起状态计数器被置为1,只有在ResumeThread被调用时,这个线程才会被执行

                        LPSECURITY_ATTRIBUTES lpSecurityAttrs)  //安全属性,指向定义线程安全属性的LPSECURITY_ATTRIBUTES结构的指针。如果为NULL,则新线程和创建线程具有同样的安全属性

    例CWinThread* pThread = AfxBeginThread(RUNTIME_CLASS(CGUIThread),0,0,NULL);

    2.使用CreateThread成员函数。使用CWinThread类的CreateThread成员函数创建并启动用户界面线程,需要先构造一个CWinThread类对象。代码如下

    CGUIThread* p_thread1 = new CGUIThread;

    p_thread1->CreateThread();//而后就可以调用SetThreadPriority等CWinThread类的成员函数对线程进行控制

    如果程序中要多次终结和启动同一个线程,采用这种两步的创建方式将非常有用

    工作者线程的创建

    工作者线程的创建,程序员不必从CWinThread派生新的线程类,只需要提供一个线程入口,也就是提供一个函数地址就可以了。在工作者线程被启动后会转入该函数,并且函数退出时线程就会结束。该线程函数的固定形式是:UINT Function(LPVOID pParam),而后就可以通过AfxBeginThread函数创建MFC工作者线程对象,此时AfxBeginThread函数的原型如下

    CWinThread* AfxBeginThread(

      AFX_THREADPROC pfnThreadProc,  //线程函数的指针,即新线程创建后要执行的函数,函数的返回值如果为0,表示线程正常结束,函数的返回值将作为线程的结束码

      LPVOID pParam,    //传给pfnThreadProc线程函数的参数

      int nPriority,

      UINT nStackSize,

      DWORD dwCreateFlag,

      LPSECURITY_ATTRIBUTES lpSecurityAttrs)

    函数的返回值是新创建的线程的指针,一般需要保存这个指针,需要时可根据指向该线程的指针来终止该线程,例如AfxBeginThread(WorkerThread,&m_staTimer);

    三、线程同步

      使隶属于同一个进程的各线程协调一致地工作称为线程同步。MFC提供了多种同步对象,如CEvent,CCriticalSection,CSemaphore,CMutex,另外,MFC也提供了线程同步辅助类CSingleLock和CMutiLock.通过这些类,可以比较容易地做到线程同步。

    Win32API提供了一组能使线程阻塞其自身执行的等待函数。这些函数只有在作为其参数的一个或多个同步对象产生信号时才会返回。在超过规定的等待时间后,不管有无信号,函数也都返回。在等待函数未返回时,线程处于等待状态,此是线程只消耗很少的CPU时间。使用等待函数既可以保证线程的同步,又可以提高程序的运行效率,其中最常见的就是WaitForSingleObject和WaitForMultipleObjects,它们分别用于监测单个同步对象和检测多个同步对象。

     多线程题:

    1.多线程同步与互斥有几种实现方法?都是什么?(C++)

    临界区(CS:critical section)、事件(Event)、互斥量(Mutex)、信号量(semaphores),需要注意的是,临界区是效率最高的,因为基本不需要其 他的开销,二内核对象涉及到用户态和内核态的切换,开销较大,另外,关键段、互斥量具有 线程所有权 的概念,因此只可以用于线程之间互斥,而不能用到 同步中。只有互斥量能完美解决进程意外终止所造成的“遗弃问题”。

    2.多线程同步和互斥有何异同,在什么情况下分别使用他们?举例说明

    所谓同步,表示有先有后,比较正式的解释是“线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。”所谓互斥,比较正式的说明是“线程互斥是指对于共享的进程系统资源,在各单个线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。线程互斥可以看成是一种特殊的线程同步。”表示不能同时访问,也是个顺序问题,所以互斥是一种特殊的同步操作。

    举个例子,设有一个全局变量global,为了保证线程安全,我们规定只有当主线程修改了global之后下一个子线程才能访问global,这就需要同步主线程与子线程,可用关键段实现。当一个子线程访问global的时候另一个线程不能访问global,那么就需要互斥。

    3.以下多线程对int型变量x的操作,哪几个需要进行同步: A. x=y;      B. x++;    C. ++x;    D. x=1;

    答案是ABC,显然,y的写入与x读y要同步,x++和++x都要知道x之前的值,所以也要同步。

    4.多线程中栈与堆是公有的还是私有的

    A:栈公有, 堆私有

    B:栈公有,堆公有

    C:栈私有, 堆公有

    D:栈私有,堆私有

    答案是C,栈一般存放局部变量,而程序员一般自己申请和释放堆中的数据(详见 堆与栈的区别 )。

    5.在Windows编程中互斥量与临界区比较类似,请分析一下二者的主要区别。

    临界区、互斥量、信号灯、事件的区别总结

    针对这个题目的话,答案主要有以下几点:

    1)互斥量是内核对象,所以它比临界区更加耗费资源,但是它可以命名,因此可以被其它进程访问

    2)从目的是来说,临界区是通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。 互斥量是为协调共同对一个共享资源的单独访问而设计的。 

    h.一个全局变量tally,两个线程并发执行(代码段都是ThreadProc),问两个线程都结束后,tally取值范围。

    inttally = 0; //global

    voidThreadProc() {

    for (inti = 1; i <= 50; i++)

    tally += 1;

    }

    当两线程串行时,结果最大为100,当某个线程运行结束,而此时另外一个线程刚取出0,还未计算时,结果最小为50

  • 相关阅读:
    vector常规用法(leedcode)
    creaor小游戏数据表配置引发的相关问题
    cocos2dx conversion to dalvik format failed
    Java中如何实现列表对象(List)的灵活查找?/java中Listgen根据某个元素获取对象
    MySql忘记root用户密码
    命令行模式下备份和恢复mysql数据库
    mysql开启日志
    Python操作Excel文件
    Python操作Json文件
    Python操作xml文件
  • 原文地址:https://www.cnblogs.com/fenghuan/p/4861372.html
Copyright © 2020-2023  润新知