线程、进程

来源:互联网 发布:c语言入门推荐用书 编辑:程序博客网 时间:2024/06/05 00:30

进程是指在系统中正在运行的一个应用程序;线程是系统分配处理器时间资源的基本单元,或者说进程之内独立执行的一个单元。对于操作系统而言其调度单元是线程。一个进程至少包括一个线程,通常将该线程称为主线程。一个进程从主线程的执行开始进而创建一个或多个附加线程,就是所谓基于多线程的多任务。


那进程与线程的区别到底是什么?进程是执行程序的实例。例如,当你运行记事本程序(Nodepad)时,你就创建了一个用来容纳组成Notepad.exe的代码及其所需调用动态链接库的进程。每个进程均运行在其专用且受保护的地址空间内。因此,如果你同时运行记事本的两个拷贝,该程序正在使用的数据在各自实例中是彼此独立的。在记事本的一个拷贝中将无法看到该程序的第二个实例打开的数据。


实际上线程运行而进程不运行。两个进程彼此获得专用数据或内存的唯一途径就是通过协议来共享内存块。这是一种协作策略。下面让我们分析一下任务管理器里的进程选项卡。


这里的进程是指一系列进程,这些进程是由它们所运行的可执行程序实例来识别的,这就是进程选项卡中的第一列给出了映射名称的原因。请注意,这里并没有进程名称列。进程并不拥有独立于其所归属实例的映射名称。换言之,如果你运行5个记事本拷贝,你将会看到5个称为Notepad.exe的进程。它们是如何彼此区别的呢?其中一种方式是通过它们的进程ID,因为每个进程都拥有其独一无二的编码。该进程ID由Windows NT或Windows 2000生成,并可以循环使用。因此,进程ID将不会越编越大,它们能够得到循环利用。第三列是被进程中的线程所占用的CPU时间百分比。它不是CPU的编号,而是被进程占用的CPU时间百分比。此时我的系统基本上是空闲的。尽管系统看上去每一秒左右都只使用一小部分CPU时间,但该系统空闲进程仍旧耗用了大约99%的CPU时间。


1、进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 


一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行 

      



线程的划分尺度小于进程,使得多线程程序的并发性高。 


另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。


线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 


从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别



下面是上面几个函数的程序例子:

#include <pthread.h>

#include <sched.h>

 

void *child_thread(void *arg)

{

int policy;

int max_priority, min_priority;

struct sched_param param;

pthread_attr_t attr;

 

pthread_attr_init(&attr); /*初始化线程属性变量*/

pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); /*设置线程继承性*/

pthread_attr_getinheritsched(&attr, &policy); /*获得线程的继承性*/

if(policy==PTHREAD_EXPLICIT_SCHED)

    printf(“Inheritsched: PTHREAD_EXPLICIT_SCHED\n”);

if(policy==PTHREAD_INHERIT_SCHED)

    printf(“Inheritsched: PTHREAD_INHERIT_SCHED\n”);

 

pthread_attr_setschedpolicy(&attr, SCHED_RR);/*设置线程调度策略*/

pthread_attr_getschedpolicy(&attr, &policy);/*取得线程的调度策略*/

if(policy == SCHED_FIFO)

    printf(“Schedpolicy: SCHED_FIFO\n”);

if(policy == SCHED_RR)

    printf(“Schedpolicy: SCHED_RR\n”);

if(policy == SCHED_OTHER)

    printf(“Schedpolicy: SCHED_OTHER\n”);

 

max_priority = sched_get_priority_max(policy);/*获得系统支持的线程优先权的最大值*/

min_priority = sched_get_priority_min(policy);/* 获得系统支持的线程优先权的最小值*/

printf(“Max priority: %u\n”, max_priority);

printf(“Min priority: %u\n”, min_priority);

 

param.sched_priority = max_priority;

pthread_attr_setschedparam(&attr, &param);/*设置线程的调度参数*/

printf(“sched_priority: %u\n”, param.sched_priority);/*获得线程的调度参数*/

pthread_attr_destroy(&attr);

}

 

int main(int argc,char *argv[ ])

{

pthread_t child_thread_id;

 

pthread_create(&child_thread_id, NULL, child_thread,NULL);

pthread_join(child_thread_id, NULL);

}

六、线程的作用域

    函数pthread_attr_setscope和pthread_attr_getscope分别用来设置和得到线程的作用域,这两个函数的定义如下:

 

名称::

pthread_attr_setscope

pthread_attr_getscope

功能:

获得/设置线程的作用域

头文件:

#include <pthread.h>

函数原形:

int pthread_attr_setscope(pthread_attr_t *attr,int scope);

int pthread_attr_getscope(const pthread_attr_t *attr,int *scope);

参数:

attr           线程属性变量

scope         线程的作用域

返回值:

若成功返回0,若失败返回-1。

 

 

 

 

 

 

 

 

 

这两个函数具有两个参数,第1个是指向属性对象的指针,第2个是作用域或指向作用域的指针,作用域控制线程是否在进程内或在系统级上竞争资源,可能的值是PTHREAD_SCOPE_PROCESS(进程内竞争资源)PTHREAD_SCOPE_SYSTEM.(系统级上竞争资源)。

 

 

七、线程堆栈的大小

    函数pthread_attr_setstacksize和pthread_attr_getstacksize分别用来设置和得到

名称::

pthread_attr_getdetstacksize

pthread_attr_setstacksize

功能:

获得/修改线程栈的大小

头文件:

#include <pthread.h>

函数原形:

int pthread_attr_getstacksize(const pthread_attr_t *restrict attr,size_t *restrict stacksize);

int pthread_attr_setstacksize(pthread_attr_t *attr ,size_t *stacksize);

参数:

attr           线程属性变量

stacksize       堆栈大小  

返回值:

若成功返回0,若失败返回-1。

 

 

 

 

 

 

 

 

 

 

这两个参数具有两个参数,第1个是指向属性对象的指针,第2个是堆栈大小或指向堆栈大小的指针

如果希望改变栈的默认大小,但又不想自己处理线程栈的分配问题,这时使用pthread_attr_setstacksize函数就非常用用。 


^^^^^^^^^^^^^^^^^……………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………


线程 (detach的作用)


线程状态

在一个线程的生存期内,可以在多种状态之间转换。不同操作系统可以实现不同的线程模型,定义许多不同的线程状态,每个状


态还可以包含多个子状态。但大体说来,如下几种状态是通用的:

       就绪:参与调度,等待被执行。一旦被调度选中,立即开始执行。

       运行:占用CPU,正在运行中。

       休眠:暂不参与调度,等待特定事件发生。

       中止:已经运行完毕,等待回收线程资源(要注意,这个很容易误解,后面解释)。

线程环境

线程存在于进程之中。进程内所有全局资源对于内部每个线程均是可见的。

进程内典型全局资源有如下几种:

       代码区。这意味着当前进程空间内所有可见的函数代码,对于每个线程来说也是可见的。

       静态存储区。全局变量。静态变量。

       动态存储区。也就是堆空间。

线程内典型的局部资源有:

       本地栈空间。存放本线程的函数调用栈,函数内部的局部变量等。

       部分寄存器变量。例如本线程下一步要执行代码的指针偏移量。


一个进程发起之后,会首先生成一个缺省的线程,通常称这个线程为主线程。C/C++程序中主线程就是通过main函数进入的线程


。由主线程衍生的线程称为从线程,从线程也可以有自己的入口函数,作用相当于主线程的main函数。


这个函数由用户指定。Pthread和winapi中都是通过传入函数指针实现。在指定线程入口函数时,也可以指定入口函数的参数。


就像main函数有固定的格式要求一样,线程的入口函数一般也有固定的格式要求,参数通常都是void *类型,返回类型在


pthread中是void *, winapi中是unsigned int,而且都需要是全局函数。


最常见的线程模型中,除主线程较为特殊之外,其他线程一旦被创建,相互之间就是对等关系 (peer to peer), 不存在隐含的


层次关系。每个进程可以创建的最大线程数由具体实现决定。


为了更好的理解上述概念,下面通过具体代码来详细说明。

线程类接口定义

一个线程类无论具体执行什么任务,其基本的共性无非就是

       创建并启动线程

       停止线程

       另外还有就是能睡,能等,能分离执行(有点拗口,后面再解释)。

       还有其他的可以继续加…

将线程的概念加以抽象,可以为其定义如下的类:

文件 thread.h

#ifndef __THREAD__H_

#define __THREAD__H_

class Thread

{

public:

Thread();

virtual ~Thread();

int start (void * = NULL);

void stop();

void sleep (int);

void detach();

void * wait();

protected:

virtual void * run(void *) = 0;

private:

//这部分win和unix略有不同,先不定义,后面再分别实现。

//顺便提一下,我很不习惯写中文注释,这里为了更明白一

//点还是选用中文。

… 

}; 

#endif


Thread::start()函数是线程启动函数,其输入参数是无类型指针。

Thread::stop()函数中止当前线程。

Thread::sleep()函数让当前线程休眠给定时间,单位为秒。

Thread::run()函数是用于实现线程类的线程函数调用。

Thread::detach()和thread::wait()函数涉及的概念略复杂一些。在稍后再做解释。


Thread类是一个虚基类,派生类可以重载自己的线程函数。下面是一个例子。


示例程序


代码写的都不够精致,暴力类型转换比较多,欢迎有闲阶级美化,谢过了先。

文件create.h

#ifndef __CREATOR__H_

#define __CREATOR__H_


#include <stdio.h>

#include "thread.h"


class Create: public Thread

{

protected:

void * run(void * param)

{

    char * msg = (char*) param;

    printf ("%s\n", msg);

    //sleep(100); 可以试着取消这行注释,看看结果有什么不同。

    printf("One day past.\n");

    return NULL;

}

};

#endif

然后,实现一个main函数,来看看具体效果:

文件Genesis.cpp

#include <stdio.h>

#include "create.h"


int main(int argc, char** argv)

{

Create monday;

Create tuesday;


printf("At the first God made the heaven and the earth.\n");

monday.start("Naming the light, Day, and the dark, Night, the first day.");

tuesday.start("Gave the arch the name of Heaven, the second day.");

printf("These are the generations of the heaven and the earth.\n");


return 0;

}

编译运行,程序输出如下:

At the first God made the heaven and the earth.

These are the generations of the heaven and the earth.

令人惊奇的是,由周一和周二对象创建的子线程似乎并没有执行!这是为什么呢?别急,在最后的printf语句之前加上如下语句


monday.wait();

tuesday.wait();

重新编译运行,新的输出如下:

At the first God made the heaven and the earth.

Naming the light, Day, and the dark, Night, the first day.

One day past.

Gave the arch the name of Heaven, the second day.

One day past.

These are the generations of the heaven and the earth.


为了说明这个问题,需要了解前面没有解释的Thread::detach()和Thread::wait()两个函数的含义。


无论在windows中,还是Posix中,主线程和子线程的默认关系是:

无论子线程执行完毕与否,一旦主线程执行完毕退出,所有子线程执行都会终止。这时整个进程结束或僵死(部分线程保持一种


终止执行但还未销毁的状态,而进程必须在其所有线程销毁后销毁,这时进程处于僵死状态),在第一个例子的输出中,可以看


到子线程还来不及执行完毕,主线程的main()函数就已经执行完毕,从而所有子线程终止。


需要强调的是,线程函数执行完毕退出,或以其他非常方式终止,线程进入终止态(请回顾上面说的线程状态),但千万要记住


的是,进入终止态后,为线程分配的系统资源并不一定已经释放,而且可能在系统重启之前,一直都不能释放。终止态的线程,


仍旧作为一个线程实体存在与操作系统中。(这点在win和unix中是一致的。)而什么时候销毁线程,取决于线程属性。


通常,这种终止方式并非我们所期望的结果,而且一个潜在的问题是未执行完就终止的子线程,除了作为线程实体占用系统资源


之外,其线程函数所拥有的资源(申请的动态内存,打开的文件,打开的网络端口等)也不一定能释放。所以,针对这个问题,


主线程和子线程之间通常定义两种关系:

      可会合(joinable)。这种关系下,主线程需要明确执行等待操作。在子线程结束后,主线程的等待操作执行完毕,子线程


和主线程会合。这时主线程继续执行等待操作之后的下一步操作。主线程必须会合可会合的子线程,Thread类中,这个操作通过


在主线程的线程函数内部调用子线程对象的wait()函数实现。这也就是上面加上三个wait()调用后显示正确的原因。必须强调的


是,即使子线程能够在主线程之前执行完毕,进入终止态,也必需显示执行会合操作,否则,系统永远不会主动销毁线程,分配


给该线程的系统资源(线程id或句柄,线程管理相关的系统资源)也永远不会释放。

      相分离(detached)。顾名思义,这表示子线程无需和主线程会合,也就是相分离的。这种情况下,子线程一旦进入终止态


,系统立即销毁线程,回收资源。无需在主线程内调用wait()实现会合。Thread类中,调用detach()使线程进入detached状态。


这种方式常用在线程数较多的情况,有时让主线程逐个等待子线程结束,或者让主线程安排每个子线程结束的等待顺序,是很困


难或者不可能的。所以在并发子线程较多的情况下,这种方式也会经常使用。

缺省情况下,创建的线程都是可会合的。可会合的线程可以通过调用detach()方法变成相分离的线程。但反向则不行。


UNIX实现


文件 thread.h

#ifndef __THREAD__H_

#define __THREAD__H_

class Thread

{

public:

Thread();

virtual ~Thread();

int start (void * = NULL);

void stop();

void sleep (int);

void detach();

void * wait();

protected:

virtual void * run(void *) = 0;

private:

pthread_t handle;

bool started;

bool detached;

void * threadFuncParam;

friend void * threadFunc(void *);

};


//pthread中线程函数必须是一个全局函数,为了解决这个问题

//将其声明为静态,以防止此文件之外的代码直接调用这个函数。

//此处实现采用了称为Virtual friend function idiom 的方法。

Static void * threadFunc(void *); 

#endif


文件thread.cpp

#include <pthread.h>

#include <sys/time.h>

#include “thread.h”


static void * threadFunc (void * threadObject)

{

Thread * thread = (Thread *) threadObject;

return thread->run(thread->threadFuncParam);

}


Thread::Thread()

{

started = detached = false;

}


Thread::~Thread()

{

stop();

}


bool Thread::start(void * param)

{

pthread_attr_t attributes;

pthread_attr_init(&attributes);

if (detached)

{

    pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED);

}


threadFuncParam = param;


if (pthread_create(&handle, &attributes, threadFunc, this) == 0)

{

    started = true;

}


pthread_attr_destroy(&attribute);

}



void Thread::detach()

{

if (started && !detached)

{

    pthread_detach(handle);

}

detached = true;

}


void * Thread::wait()

{

void * status = NULL;

if (started && !detached)

{

    pthread_join(handle, &status);

}

return status;

}


void Thread::stop()

{

if (started && !detached)

{

    pthread_cancel(handle);

    pthread_detach(handle);

    detached = true;

}


void Thread::sleep(unsigned int milliSeconds)

{

timeval timeout = { milliSeconds/1000, millisecond%1000};

select(0, NULL, NULL, NULL, &timeout);

}



Windows实现


文件thread.h

#ifndef _THREAD_SPECIFICAL_H__

#define _THREAD_SPECIFICAL_H__


#include <windows.h>


static unsigned int __stdcall threadFunction(void *);


class Thread {

        friend unsigned int __stdcall threadFunction(void *);

public:

        Thread();

        virtual ~Thread();

        int start(void * = NULL);

        void * wait();

        void stop();

        void detach();

        static void sleep(unsigned int);


protected:

        virtual void * run(void *) = 0;


private:

        HANDLE threadHandle;

        bool started;

        bool detached;

        void * param;

        unsigned int threadID;

};


#endif


文件thread.cpp

#include "stdafx.h"

#include <process.h>

#include "thread.h"


unsigned int __stdcall threadFunction(void * object)

{

        Thread * thread = (Thread *) object;

        return (unsigned int ) thread->run(thread->param);

}


Thread::Thread()

{

        started = false;

        detached = false;

}


Thread::~Thread()

{

        stop();

}


int Thread::start(void* pra)

{

        if (!started)

        {

                param = pra;

                if (threadHandle = (HANDLE)_beginthreadex(NULL, 0, threadFunction, this, 0, &threadID))

                {

                        if (detached)

                        {

                                CloseHandle(threadHandle);

                        }

                        started = true;

                }

        }

        return started;

}


//wait for current thread to end.

void * Thread::wait()

{

        DWORD status = (DWORD) NULL;

        if (started && !detached)

        {

                WaitForSingleObject(threadHandle, INFINITE);

                GetExitCodeThread(threadHandle, &status);       

                CloseHandle(threadHandle);

                detached = true;

        }


        return (void *)status;

}


void Thread::detach()

{

if (started && !detached)

{

    CloseHandle(threadHandle);

}

detached = true;

}


void Thread::stop()

{

        if (started && !detached)

        {

                TerminateThread(threadHandle, 0);


                //Closing a thread handle does not terminate 

                //the associated thread. 

                //To remove a thread object, you must terminate the thread, 

                //then close all handles to the thread.

                //The thread object remains in the system until 

                //the thread has terminated and all handles to it have been 

                //closed through a call to CloseHandle

                CloseHandle(threadHandle);

                detached = true;

        }

}


void Thread::sleep(unsigned int delay)

{

        ::Sleep(delay);

}



小结


本节的主要目的是帮助入门者建立基本的线程概念,以此为基础,抽象出一个最小接口的通用线程类。在示例程序部分,初学者


可以体会到并行和串行程序执行的差异。有兴趣的话,大家可以在现有线程类的基础上,做进一步的扩展和尝试。如果觉得对线


程的概念需要进一步细化,大家可以进一步扩展和完善现有Thread类。


想更进一步了解的话,一个建议是,可以去看看其他语言,其他平台的线程库中,线程类抽象了哪些概念。比如Java, perl等跨


平台语言中是如何定义的,微软从winapi到dotnet中是如何支持多线程的,其线程类是如何定义的。这样有助于更好的理解线程


的模型和基础概念。


另外,也鼓励大家多动手写写代码,在此基础上尝试写一些代码,也会有助于更好的理解多线程程序的特点。比如,先开始的线


程不一定先结束。线程的执行可能会交替进行。把printf替换为cout可能会有新的发现,等等。


每个子线程一旦被创建,就被赋予了自己的生命。管理不好的话,一只特例独行的猪是非常让人头痛的。


对于初学者而言,编写多线程程序可能会遇到很多令人手足无措的bug。往往还没到考虑效率,避免死锁等阶段就问题百出,而


且很难理解和调试。这是非常正常的,请不要气馁,后续文章会尽量解释各种常见问题的原因,引导大家避免常见错误。目前能


想到入门阶段常遇到的问题是:

       内存泄漏,系统资源泄漏。

       程序执行结果混乱,但是在某些点插入sleep语句后结果又正确了。

       程序crash, 但移除或添加部分无关语句后,整个程序正常运行(假相)。

       多线程程序执行结果完全不合逻辑,出于预期。


本文至此,如果自己动手改改,试一些例子,对多线程程序应该多少有一些感性认识了。刚开始只要把基本概念弄懂了,后面可


以一步一步搭建出很复杂的类。不过刚开始不要贪多,否则会欲速则不达,越弄越糊涂




……………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………


什么是线程同步



在程序中使用多线程时,一般很少有多个线程能在其生命期内进行完全独立的操作。更多的情况是一些线程进行某些处理操作,而其他的线程必须对其处理结果进行了解。正常情况下对这种处理结果的了解应当在其处理任务完成后进行。


  如果不采取适当的措施,其他线程往往会在线程处理任务结束前就去访问处理结果,这就很有可能得到有关处理结果的错误了解。例如,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。


  为了确保读线程读取到的是经过修改的变量,就必须在向变量写入数据时禁止其他线程对其的任何访问,直至赋值过程结束后再解除对其他线程的访问限制。象这种保证线程能了解其他线程任务处理结束后的处理结果而采取的保护措施即为线程同步。


  线程同步是一个非常大的话题,包括方方面面的内容。从大的方面讲,线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。用户模式中线程的同步方法主要有原子访问和临界区等方法。其特点是同步速度特别快,适合于对线程运行速度有严格要求的场合。


  内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这种同步机制使用了内核对象,使用时必须将线程从用户模式切换到内核模式,而这种转换一般要耗费近千个CPU周期,因此同步速度较慢,但在适用性上却要远优于用户模式的线程同步方式。


临界区


  临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。


  临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须经过InitializeCriticalSection()的初始化后才能使用,而且必须确保所有线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。否则临界区将不会起到应有的作用,共享资源依然有被破坏的可能。


onclick="javascript:window.open(this.src);" onload="javascript:if(this.width>500){this.resized=true;this.style.width=500;}" border=0>


图1 使用临界区保持线程同步


下面通过一段代码展示了临界区在保护多线程访问的共享资源中的作用。通过两个线程来分别对全局变量g_cArray[10]进行写入操作,用临界区结构对象g_cs来保持线程的同步,并在开启线程前对其进行初始化。为了使实验效果更加明显,体现出临界区的作用,在线程函数对共享资源g_cArray[10]的写入时,以Sleep()函数延迟1毫秒,使其他线程同其抢占CPU的可能性增大。如果不使用临界区对其进行保护,则共享资源数据将被破坏(参见图1(a)所示计算结果),而使用临界区对线程保持同步后则可以得到正确的结果(参见图1(b)所示计算结果)。代码实现清单附下:


// 临界区结构对象

CRITICAL_SECTION g_cs;

// 共享资源 

char g_cArray[10];

UINT ThreadProc10(LPVOID pParam)

{

 // 进入临界区

 EnterCriticalSection(&g_cs);

 // 对共享资源进行写入操作

 for (int i = 0; i < 10; i++)

 {

  g_cArray[i] = 'a';

  Sleep(1);

 }

 // 离开临界区

 LeaveCriticalSection(&g_cs);

 return 0;

}

UINT ThreadProc11(LPVOID pParam)

{

 // 进入临界区

 EnterCriticalSection(&g_cs);

 // 对共享资源进行写入操作

 for (int i = 0; i < 10; i++)

 {

  g_cArray[10 - i - 1] = 'b';

  Sleep(1);

 }

 // 离开临界区

 LeaveCriticalSection(&g_cs);

 return 0;

}

……

void CSample08View::OnCriticalSection() 

{

 // 初始化临界区

 InitializeCriticalSection(&g_cs);

 // 启动线程

 AfxBeginThread(ThreadProc10, NULL);

 AfxBeginThread(ThreadProc11, NULL);

 // 等待计算完毕

 Sleep(300);

 // 报告计算结果

 CString sResult = CString(g_cArray);

 AfxMessageBox(sResult);

}


  在使用临界区时,一般不允许其运行时间过长,只要进入临界区的线程还没有离开,其他所有试图进入此临界区的线程都会被挂起而进入到等待状态,并会在一定程度上影响。程序的运行性能。尤其需要注意的是不要将等待用户输入或是其他一些外界干预的操作包含到临界区。如果进入了临界区却一直没有释放,同样也会引起其他线程的长时间等待。换句话说,在执行了EnterCriticalSection()语句进入临界区后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。可以通过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。


  MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的,只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。对于上述代码,可通过CCriticalSection类将其改写如下:


// MFC临界区类对象

CCriticalSection g_clsCriticalSection;

// 共享资源 

char g_cArray[10];

UINT ThreadProc20(LPVOID pParam)

{

 // 进入临界区

 g_clsCriticalSection.Lock();

 // 对共享资源进行写入操作

 for (int i = 0; i < 10; i++)

 {

  g_cArray[i] = 'a';

  Sleep(1);

 }

 // 离开临界区

 g_clsCriticalSection.Unlock();

 return 0;

}

UINT ThreadProc21(LPVOID pParam)

{

 // 进入临界区

 g_clsCriticalSection.Lock();

 // 对共享资源进行写入操作

 for (int i = 0; i < 10; i++)

 {

  g_cArray[10 - i - 1] = 'b';

  Sleep(1);

 }

 // 离开临界区

 g_clsCriticalSection.Unlock();

 return 0;

}

……

void CSample08View::OnCriticalSectionMfc() 

{

 // 启动线程

 AfxBeginThread(ThreadProc20, NULL);

 AfxBeginThread(ThreadProc21, NULL);

 // 等待计算完毕

 Sleep(300);

 // 报告计算结果

 CString sResult = CString(g_cArray);

 AfxMessageBox(sResult);

}


管理事件内核对象


  在前面讲述线程通信时曾使用过事件内核对象来进行线程间的通信,除此之外,事件内核对象也可以通过通知操作的方式来保持线程的同步。对于前面那段使用临界区保持线程同步的代码可用事件对象的线程同步方法改写如下:


// 事件句柄

HANDLE hEvent = NULL;

// 共享资源 

char g_cArray[10];

……

UINT ThreadProc12(LPVOID pParam)

{

 // 等待事件置位

 WaitForSingleObject(hEvent, INFINITE);

 // 对共享资源进行写入操作

 for (int i = 0; i < 10; i++)

 {

  g_cArray[i] = 'a';

  Sleep(1);

 }

 // 处理完成后即将事件对象置位

 SetEvent(hEvent);

 return 0;

}

UINT ThreadProc13(LPVOID pParam)

{

 // 等待事件置位

 WaitForSingleObject(hEvent, INFINITE);

 // 对共享资源进行写入操作

 for (int i = 0; i < 10; i++)

 {

  g_cArray[10 - i - 1] = 'b';

  Sleep(1);

 }

 // 处理完成后即将事件对象置位

 SetEvent(hEvent);

 return 0;

}

……

void CSample08View::OnEvent() 

{

 // 创建事件

 hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

 // 事件置位

 SetEvent(hEvent);

 // 启动线程

 AfxBeginThread(ThreadProc12, NULL);

 AfxBeginThread(ThreadProc13, NULL);

 // 等待计算完毕

 Sleep(300);

 // 报告计算结果

 CString sResult = CString(g_cArray);

 AfxMessageBox(sResult);

}


  在创建线程前,首先创建一个可以自动复位的事件内核对象hEvent,而线程函数则通过WaitForSingleObject()等待函数无限等待hEvent的置位,只有在事件置位时WaitForSingleObject()才会返回,被保护的代码将得以执行。对于以自动复位方式创建的事件对象,在其置位后一被WaitForSingleObject()等待到就会立即复位,也就是说在执行ThreadProc12()中的受保护代码时,事件对象已经是复位状态的,这时即使有ThreadProc13()对CPU的抢占,也会由于WaitForSingleObject()没有hEvent的置位而不能继续执行,也就没有可能破坏受保护的共享资源。在ThreadProc12()中的处理完成后可以通过SetEvent()对hEvent的置位而允许ThreadProc13()对共享资源g_cArray的处理。这里SetEvent()所起的作用可以看作是对某项特定任务完成的通知。


  使用临界区只能同步同一进程中的线程,而使用事件内核对象则可以对进程外的线程进行同步,其前提是得到对此事件对象的访问权。可以通过OpenEvent()函数获取得到,其函数原型为:


HANDLE OpenEvent(

 DWORD dwDesiredAccess, // 访问标志

 BOOL bInheritHandle, // 继承标志

 LPCTSTR lpName // 指向事件对象名的指针

);


  如果事件对象已创建(在创建事件时需要指定事件名),函数将返回指定事件的句柄。对于那些在创建事件时没有指定事件名的事件内核对象,可以通过使用内核对象的继承性或是调用DuplicateHandle()函数来调用CreateEvent()以获得对指定事件对象的访问权。在获取到访问权后所进行的同步操作与在同一个进程中所进行的线程同步操作是一样的。


  如果需要在一个线程中等待多个事件,则用WaitForMultipleObjects()来等待。WaitForMultipleObjects()与WaitForSingleObject()类似,同时监视位于句柄数组中的所有句柄。这些被监视对象的句柄享有平等的优先权,任何一个句柄都不可能比其他句柄具有更高的优先权。WaitForMultipleObjects()的函数原型为:


DWORD WaitForMultipleObjects(

 DWORD nCount, // 等待句柄数

 CONST HANDLE *lpHandles, // 句柄数组首地址

 BOOL fWaitAll, // 等待标志

 DWORD dwMilliseconds // 等待时间间隔

);


  参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在饫锏淖饔糜朐赪aitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回WAIT_TIMEOUT。如果返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某个值,则说明所有指定对象的状态均为已通知状态(当fWaitAll为TRUE时)或是用以减去WAIT_OBJECT_0而得到发生通知的对象的索引(当fWaitAll为FALSE时)。如果返回值在WAIT_ABANDONED_0与WAIT_ABANDONED_0+nCount-1之间,则表示所有指定对象的状态均为已通知,且其中至少有一个对象是被丢弃的互斥对象(当fWaitAll为TRUE时),或是用以减去WAIT_OBJECT_0表示一个等待正常结束的互斥对象的索引(当fWaitAll为FALSE时)。下面给出的代码主要展示了对WaitForMultipleObjects()函数的使用。通过对两个事件内核对象的等待来控制线程任务的执行与中途退出:


// 存放事件句柄的数组

HANDLE hEvents[2];

UINT ThreadProc14(LPVOID pParam)

 // 等待开启事件

 DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);

 // 如果开启事件到达则线程开始执行任务

 if (dwRet1 == WAIT_OBJECT_0)

 {

  AfxMessageBox("线程开始工作!");

  while (true)

  {

   for (int i = 0; i < 10000; i++);

   // 在任务处理过程中等待结束事件 

   DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0);

   // 如果结束事件置位则立即终止任务的执行

   if (dwRet2 == WAIT_OBJECT_0 + 1)

    break;

  }

 }

 AfxMessageBox("线程退出!");

 return 0;

}

……

void CSample08View::OnStartEvent() 

{

 // 创建线程

 for (int i = 0; i < 2; i++)

  hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);

  // 开启线程

  AfxBeginThread(ThreadProc14, NULL);

  // 设置事件0(开启事件)

  SetEvent(hEvents[0]);

}

void CSample08View::OnEndevent() 

{

 // 设置事件1(结束事件)

 SetEvent(hEvents[1]);

}

 


……………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………



线程学习

多线程:

题目一:火车站三个窗口各有100张票出售

 

题目一

利用多线程去执行相同的代码,结果显示这三个线程并不是依次交替执行,而是在三个线程同时被执行的情况下,有的线程被分配时间片的机会多,票被提前卖完,而有的线程被分配时间片的机会比较少,票迟一些卖完。可见,利用扩展Thread类创建的多个线程(还有一种方法是实现接口Runnable),虽然执行的是相同的代码,但彼此相互独立,且各自拥有自己的资源,互不干扰。

题目二:火车站三个窗口共有100张票出售

1、先将上面的改成实现接口Runnable

2、共用ticket=100这个字段,只要参数传递同一个对象就OK,main函数如下

题目二

     线程的run()方法执行完毕时,该线程就被视为进入了消亡状态。一个处于消亡状态的线程不能再进入其他状态,即使对它调用 start()方法也无济于事。可以通过两种方法使线程进入消亡状态:自然撤消(线程执行完)或是强行中止,要终止一个线程,需要通过方法interrupt()来实现。

 

题目三:线程1执行完后再去执行线程2

 

(1)调用线程类的isAlive()方法和sleep()方法来等待某个或某些线程结束

       t1.start();

       while (t1.isAlive()) {

           try {

              Thread.sleep(3000);//主线程休息

           } catch (InterruptedException e) {

                  e.printStackTrace();

           }

       }

       t2.start();

 

(2)调用线程类的join()方法来等待某个或某些线程结束

Thread类中的join()方法也可以用来等待一个线程的结束,而且这个方法更为常用,它的语法格式如下所示:

public final void join() throws InterruptedException

代码如下:

    t1.start();

    try {

       t1.join(); //当前线程等待线程t1 执行完后再继续往下执行

    } catch (InterruptedException e) {

       e.printStackTrace();

    }

    t2.start();

    t3.start();

 

 

其他:

线程分为两类:用户线程和守护线程(又称为后台线程)。用户线程是那些完成有用工作的线程,也就是前面所说的一般线程。守护线程是那些仅提供辅助功能的线程。这类线程可以监视其他线程的运行情况,也可以处理一些相对不太紧急的任务。在一些特定的场合,经常会通过设置守护线程的方式来配合其他线程一起完成特定的功能。

 

线程终止

通过对结果的分析,可以发现,用户线程在调用了interrupt()方法之后并没有被中断,而是继续执行,直到人为地按下Ctrl+C或者Pause键为止。这个例子说明一个事实,直接使用interrput()方法并不能中断一个正在运行的线程。它可以使一个被阻塞的线程抛出一个中断异常,从而使线程提前结束阻塞状态,退出堵塞代码。

具体步骤


在这种方式中,之所以引入共享变量,是因为该变量可以被多个执行相同任务的线程用来作为是否中断的信号,通知中断线程的执行。下面通过在程序中引入共享变量来改进代码如下所示:

 

线程终止(通过共享变量)

在使用共享变量来中断一个线程的过程中,线程体通过循环来周期性的检查这一变量的状态。如果变量的状态改变,说明程序发出了立即中断该线程的请求,此时,循环体条件不再满足,结束循环,进而结束线程的任务。

为了更加安全起见,通常需要将共享变量定义为volatile类型或者将对该共享变量的一切访问封装到同步的代码或者同步方法中去。

在多线程的程序中,当出现有两个或多个线程共享同一实例变量的情况时,每一个线程可以保持这个实例变量自己的私有副本,变量的实际备份在不同时间被更新。而问题就是变量的主备份总是需要反映它的当前状态,此时反而使效率降低。为保证效率,只需要简单地指定变量为volatile类型即可,它可以告诉编译器必须总是使用volatile变量的主备份(或者至少总是保持任何私有的备份和最新的备份一样,反之亦然)。同样,对主变量的访问必须同任何私有备份一样,精确地顺序执行。

 

4.5 什么是同步,如何在多线程间保持同步

什么是同步呢?当两个或多个线程需要访问同一资源时,它们需要以某种顺序来确保该资源某一时刻只能被一个线程使用的方式称为同步。(火车站售票、抢购)

(1)使用同步代码块


为了防止多个线程无序地访问共享资源,只需将对共享资源操作的关键代码放入一个同步代码块中即可。同步代码块的语法形式如下所示:


synchronized (Object)

{

    // 关键代码

}

其中,Object是需要同步的对象的引用。一般为this

(2)使用同步方法


同步方法和同步代码块的功能是一样的,都是利用互斥锁实现关键代码的同步访问。

只不过在这里通常关键代码就是一个方法的方法体,此时只需要调用synchronized关键字修饰该方法即可。一旦被synchronized关键字修饰的方法已被一个线程调用,那么所有其他试图调用同一实例中的该方法的线程都必须等待,直到该方法被调用结束后释放其锁给下一个等待的线程。

public synchronized void sale()   // 同步方法中的代码为关键代码

{

}

也就是给方法加个标记

 

在处理线程同步时还需要注意一个问题,那就是死锁。死锁是多线程程序最常见的问题之一,那么什么是死锁,为什么会发生死锁呢?


死锁问题:即由于两个或多个线程都无法得到相应的锁而造成的两个线程都等待的现象。这种现象主要是因为相互嵌套的synchronized代码段而造成。


例如,在某一多线程的程序中有两个共享资源A和B,并且每一个线程都需要获得这两个资源后才可以执行。

 

线程1已经获取资源A的锁,由于某种原因被阻塞,此时线程2启动并获得资源B的锁,再去获得资源A的锁时发现线程1已经获取,因此等待线程1释放A锁。线程1从阻塞中恢复以后继续执行,欲获取资源B的锁,却发现B锁已被线程2获得,因此也陷入等待。在这种情况下,程序已无法向前推进,在没有外力的情况下,也不会自动退出,因而造成了严重的死锁问题。

 

4.6 多个线程之间如何进行通信

因为虽然synchronized关键字可以阻止并发更新同一个共享资源,实现了同步,但是它不能用来实现线程间的消息传递,也就是所谓的通信。其实,Java提供了3个非常重要的方法来巧妙地解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。它们都是Object类的最终方法,因此每一个类都默认拥有它们。


调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行态退出,进入等待队列,直到被再次唤醒。而调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。调用notifyAll()方法可以使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态,此时,优先级最高的那个线程最先执行。


………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………

进程和线程的区别


当我们的程序中的两个功能需要同时实现,或是都需要阻塞,我们

可能会想到求助于进程或线程,但它们的具体区别是什么或许一下还

说不出来。

进程是具有一定独立功能的程序,是系统进行资源分配和调度的一个独立单位.

线程是进程的一个实体,它是比进程更小的能独立运行的基本单位.线程自己基

本上不拥有系统资源,只拥有一点在运行中必不可少的资源

(如程序计数器,一组寄存器和栈),但是它可与同属一个进程

的其他的线程共享进程所拥有的全部资源.

进程和线程的区别;

1创建和结束  


进程的创建  常用的是fork(); 还有exec();


结束时有wait(NULL)//NULL表示不关心子进程返回状态;


或pid_t waitpid(pid_t pid,int * status,int options);


例:


waitpid(pid, NULL,0 );pid为子进程进程号, status为子进程返回状态,不关心的话可以为NULL; options为0的话表示要等的子进程没结束的话就挂起,不挂起的话就用 WNOHANG;waitpid(-1,NULL,0) 相当于wait(NULL);


线程的创建 pthread_create(&tid[i], NULL, fun, &a[i]);  第一个参数为要线程号的地址;第二个参数为创建线程的类型,一般为NULl即默认;第三个参数为要调用函数的入口地址,可以是函数指针;第三个为该函数的参数;


线程的结束:pthread_join(tid[1], NULL); 第一个参数为要结束的线程号, 第二个参数为结束类型,和线程以什么方式退出有关,如不关心可以是NULL;


2所占内存资源的不同


进程有自己独立的地址空间,而线程则需要和同一进程中的其他线程共享一个地址空间,但线程却有自己独立


的栈,线程的栈是从进程的栈中切割出来的,当然它也能给子函数分配和保存栈帧;(两个不同线程调用同一子


函数时都会在自己的栈区为其创建栈帧,所以子函数可以同时被不同的线程调用);下边详细讲它们在所占内存资源上的不同

进程有自己独立的地址空间,也就是有自己的一套诸如text段,realdata段

data段、栈、堆等, 进程创建时会将父进程的地址空间复制一份,如父进程定义的全局

变量在子进程中也能有一个相同名称的,但这两个变量并不是同一个。


而对于线程,同一个进程中的多个线程共享同一个地址空间,也就是

进程的地址空间,当然线程也有自己独享的一些内存资源来保障它能相对

独立运行,比如栈。栈是个线程独有的,保存其运行状态和局部自动变量的。

栈在线程开始的时候初始化,每个线程的栈互相独立,因此,

栈是 thread safe的也就是线程安全。操作系统在切换线程的时候会自动的切换栈,

就是切换 SS/ESP寄存器。栈空间不需要在高级语言里面显式的分配和释放



每个进程都有一个栈,在这个进程中每个函数被调用是分别从

这个栈中占用一段区域称为帧(即新建一个栈顶,再有一个指针指向它)


进程的地址空间分成代码段、静态数据段、堆和栈段。线程栈的位置和

大小是从它所属的进程的栈中切分出来的。线程栈为线程调用且尚未退出

的每个例程保存一个栈帧。栈帧包含临时变量、局部变量、返回地址以及

线程回到之前执行的例程所需要的任何附加信息。一旦例程退出,

该例程的栈帧会从栈中删除。




………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………





线程


  是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。


  线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.


  线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文.多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定. 线程的运行中需要使用计算机的内存资源和CPU


  线程的周期


  新建 就绪 运行 阻塞 死亡


  线程调度与优先级


  有线程进入了就绪状态,需要有线程调度程序来决定何时执行,根据优先级来调度.


  线程组


  每个线程都是一个线程组的一个成员,线程组把多个线程集成一个对象,通过线程组可以同时对其中的多个线程进行操作.在生成线程时必须将线程放在指定的线程组,也可以放在缺省的线程组中,缺省的就是生成该线程的线程所在的线程组.一旦一个线程加入了某个线程组,不能被移出这个组.


  守护线程


  是特殊的线程,一般用于在后台为其他线程提供服务.


  isDaemon():判断一个线程是否为守护线程.


  set Daemon():设置一个线程为守护线程.


  Thread类和Runnable接口


  Thread类


  类Thread在包java.lang中定义,它的构造方法如下:


  public Thread();


  public Thread(Runnable target);


  public Thread(Runnable target,String name);


  public Thread(String name);


  public Thread(ThreadGroup group,Runnable target);


  public Thread(ThreadGroup group, String name);


  主要方法


  isActive() 判断是否处于执行状态


  Suspend() 暂停执行


  reSume 恢复执行


  start() 开始执行


  Stop() 停止执行


  sleep() 睡眠


  run() 程序体


  yield() 向其他线程退让运行权


  线程优先级


  Public statuc final int MAX_PRIORITY最高优先级,10


  Public statuc final int MIN_PRIORITY最低优先级,1


  Public statuc final int NORM_PRIORITY普通优先级,5


  Runnable接口


  Runnable接口中只定义了一个方法run()作为线程体,


  void run()


  Java的线程是通过java.lang.Thread类来实现的。


  VM启动时会有一个由主方法(public static void main(){})所定义的线程。


  可以通过创建Thread的实例来创建新的线程。


  每个线程都是通过某个特定的Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。


  通过调用Thread类的start()方法来启动一个线程


  Java里面实现多线程,有2个方法


  1 继承 Thread类,比如


  class MyThread extends Thread {


  public void run() {


  // 这里写上线程的内容


  }


  public static void main(String[] args) {


  // 使用这个方法启动一个线程


  new MyThread().start();


  }


  }


  2 实现 Runnable接口


  class MyThread implements Runnable{


  public void run() {


  // 这里写上线程的内容


  }


  public static void main(String[] args) {


  // 使用这个方法启动一个线程


  new Thread(new MyThread()).start();


  }


  }


  一般鼓励使用第二种方法,应为Java里面只允许单一继承,但允许实现多个接口。第二个方法更加灵活。


线程数


什么是线程数  首先需要明白,原始下载地址与候选资源的区别。


  原始下载地址是您建立下载任务时,该资源指向的最终下载服务器上的文件地址。


  候选资源是下载软件为用户在网络上搜集到的该文件其他下载地址。


  较早的IE下载是使用单线程的下载技术,可以简单的理解为用户端与服务器端仅仅只有一座桥梁,数据传送只能靠这一座桥梁来完成。我们可以把这个桥梁当作是线程。


  线程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作,称为多线程。


       线程数的设置


  线程数的多少,自然会影响到下载速度的多少,这样看来,下载线程数应该设置的越高越好,这样的理解是错误的。


  假设从服务端传送数据到用户端,把用户端和服务端比做两个小岛,线程数比做连接两个小岛之间的桥梁,架桥越多,单位时间内传送的数据越多,但如果桥梁架设超过双方所能承受的数量时,用户端将无法接受其他服务端的数据,而服务端将无法为其他用户端传送数据,因此,线程数的多少,要根据服务端和用户端的具体情况而定。


  目前网络中的服务端,为用户提供的连接线程数,在1—10个,用户可以根据不同的服务端限制,来修改下载软件的原始下载线程数。根据下载资源的热门程度,其候选资源数量的不同,该任务下载可用的线程数也会不同,一般可以设置在35-50之间,这样的设置不会导致您电脑的连接数过多,而无法从事其他网络活动。

原创粉丝点击