精华内容
下载资源
问答
  • windows下用户级线程不是资源分配的单位,那么在执行过程对于4核的CPU,操作系统会将一个进程下四个线程分别分配给四个核么以使得它们同时运行,提高效率?
  • 进程是具有一定独立功能程序关于某个数据集合上一次运行活动,进程是系统进行资源分配和调度一个独立单位.线程是进程一个实体,是CPU调度和分派基本单位,它是比进程更小能独立运行基本单位.线程自己基本...

           进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.线程是进程的一个实体,CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.本章主要讲解Windows Embedded Compact 7中的进程和线程。

    6.1 Windows Embedded Compact 7中的进程

    6.1.1 进程概述

    应用程序通常指的是保存在磁盘上的一个可执行文件,是静态的。当应用程序被加载到内存中并运行后,就成为一个或多个进程。在操作系统中,进程通常是指一个程序的运行实例,进程是资源分配的基本单位,所以如果用户使用写字板同时打开8个不同的文件,通过任务管理器将看到8个不同的进程。进程由两个组件构成:一个内核对象,操作系统用它来管理进程,以及一个虚拟地址空间,操作系统的虚拟地址管理保证一个进程不能非法地访问另一个进程的虚拟地址空间 (否则将导致段错误出现)。每个进程在操作系统中都被分配一个唯一的标识,称为ProcessId,在Windows CE7中是一个32位的整数,用于区分不同的进程。新的进程创建后,还将得到进程句柄 (handle),所有对进程对象的操作都通过进程句柄来完成。

    每个进程又可以包含一个或多个线程,所有的线程都是在它的地址空间中运行,共享进程的系统资源。其中一个比较特殊的是主线程,系统在创建进程的时候将自动创建它的主线程,主线程负责进程的初始化并根据需要创建其它的线程。线程是系统CPU调度和分派的基本单位,操作系统会采用动态优先级调度策略为每个线程调度一些CPU时间,为线程分配时间片,使所有线程“并发”运行。Windows CE系统是基于多线程的操作系统。

    Windows CE中的进程与桌面Windows系统中的进程相比,包含更少的状态信息,Windows CE中的进程不支持环境变量,没有当前目录,不支持安全属性,不支持句柄继承,因此在Windows CE的创建进程函数中,与上下文相关的参数大都被设置为NULL0

    6.1.2 创建进程

    Windows CE7系统中,函数CreateProcess()用于创建一个新的进程以及它的主线程,这个进程将运行参数指定的可执行文件(程序),这个函数的原型如下:

    BOOL CreateProcess(

      LPCWSTR pszImageName,

      LPCWSTR pszCmdLine,

      LPSECURITY_ATTRIBUTES psaProcess,

      LPSECURITY_ATTRIBUTES psaThread,

      BOOL fInheritHandles,

      DWORD fdwCreate,

      LPVOID pvEnvironment,

      LPWSTR pszCurDir,

      LPSTARTUPINFOW psiStartInfo,

      LPPROCESS_INFORMATION pProcInfo

    );

    这是一个比较复杂的函数,需要多达10个参数,下面分别介绍这些参数的涵义:

    l pszImageName: 指定要加载的应用程序名,这里既可以使用应用程序的完整路径,也可以使用相对路径。如果使用相对路径,则将按照下面的顺序查找目录:(1)系统的Windows目录(\windows); (2)设备的根目录(\); (3)一个OEM指定的目录。这是一个指向字符串的指针,Windows CE中,这个参数不能指定为NULL,这是因为Windows CE不支持Windows桌面系统下的将pszCmdLine参数中的第一个命令作为应用程序名的方法。应用程序名可以不指定文件扩展名,如果没有系统将自动补上EXE扩展。

    l pszCmdLine: 指定启动应用程序的命令行。该参数可以指定为NULL,表示应用程序不带参数,此时将单单使用参数pszImageName作为命令行;如果不为NULL,那么参数pszImageName指定应用程序名,pszCmdLine指定除应用程序名外的命令行参数,注意不要将应用程序名作为pszCmdLine的第一个命令传递。对于C运行时的进程,可以使用argcargv来访问命令行参数。在Windows CE下,这个参数总是以Unicode字符串传递。

    l psaProcess: 指定进程的安全属性,Windows CE不支持,设置为NULL

    l psaThread: 指定线程的安全属性,Windows CE不支持,设置为NULL

    l fInheritHandles: 指定新创建的进程是否继承父进程的句柄,Windows CE不支持,设置为NULL

    l fdwCreate: 指定控制进程优先级以及创建过程的标志,表6-1列出了Windows CE支持的值,这些值可以组合使用(使用“|”运算符)

    6-1 Windows CE支持的参数fdwCreate可取值及说明

    说明

    CREATE_NEW_CONSOLE

    为新的进程创建一个新的控制台,而不继承父进程的控制台

    CREATE_SUSPENDED

    新进程的主线程将被初始化为挂起状态,直到对应的ResumeThread()函数被调用才进入可运行状态

    DEBUG_PROCESS

    新创建进程将被调用进程调试,系统将把新创建进程的所有调试信息通知调用进程。此时新创建进程的子进程也将被调用进程调试。

    DEBUG_ONLY_THIS_PROCESS

    与DEBUG_PROCESS标志同时使用,只负责调试新创建进程,它的子进程不被调试

    INHERIT_CALLER_PRIORITY

    指定新创建进程继承调用进程的优先级

    l pvEnvironment: 指定新创建进程的环境块,Windows CE不支持,设置为NULL

    l pszCurDir: 指定进程的当前目录,Windows CE不支持,设置为NULL

    l psiStartInfo: 指定进程的启动信息,Windows CE不支持,设置为NULL

    l pProcInfo: 这是输出参数,用于返回新创建进程的标识信息,这是一个指向结构体LPPROCESS_INFORMATION的指针,如果用户不需要对进程及主线程的句柄进行处理,可以给这个参数设置为NULL。结构体LPPROCESS_INFORMATION的定义如下:

    typedef struct _PROCESS_INFORMATION {

      HANDLE hProcess;

      HANDLE hThread;

      DWORD dwProcessId;

      DWORD dwThreadId;

    } PROCESS_INFORMATION;

    其中,hProcess为返回的进程句柄,所有对进程对象的操作都通过这个进程句柄完成; hThread为返回的进程主线程的句柄,所有对主线程对象的操作都通过这个句柄完成; dwProcessId为系统分配的全局进程标识(Id),为一个32位的整数,进程标识在进程的生命期内有效,一旦进程结束,系统将自动回收,进程标识的另一个作用是作为OpenProcess()函数的参数来打开进程的句柄;dwThreadId为全局主线程标识。

    当用户提供一个PROCESS_INFORMATION结构体接收新创建进程及其主线程句柄的副本时,加上进程内部维护的句柄,此时系统内该进程句柄的引用计数将为2,所以一定要记得在使用完进程句柄后关闭句柄。如果用户不需要进程句柄,应该直接传一个NULL进来,这样系统就不会产生句柄的副本。另外一个比较常见的作法是在进程创建成功后,立即关闭得到的句柄,这样保证新创建进程结束后能够正常释放,防止内存泄漏。

    当系统成功创建进程,CreateProcess()函数返回非0,创建失败则返回0。通过GetLastError()函数能够得到关于错误的说明信息。

    下面的例子演示如何使用CreateProcess()函数创建一个进程,该进程将启动Windos CE7下的任务管理器。

    (1) 新建一个项目,项目类型为VC++下的智能设备,使用MFC只能设备应用程序向导创建一个基于对话框的应用程序,输入工程名,这里使用CreateProcess

    (2) 在对话框内添加一个列表框,和一个按钮,如图6.1所示。在列表框内添加几个Windows CE下常用的应用,如命令行,IE浏览器,写字板。注意在Windows CE下,IE浏览器的应用程序名变为”iesample.exe”,而不是之前我们熟知的”iexplore.exe”。用户首先选择一个应用,然后单击下面的”Create a Process”按钮,就将新开一个进程,启动指定的应用。

    6.1 创建进程运行结果

    首先需要在对话框内的初始化函数内,对列表框进行初始化:

    BOOL CCreateProcessDlg::OnInitDialog()

    {

    CDialog::OnInitDialog();

    // Set the icon for this dialog.  The framework does this automatically

    //  when the application's main window is not a dialog

    SetIcon(m_hIcon, TRUE); // Set big icon

    SetIcon(m_hIcon, FALSE); // Set small icon

    // TODO: Add extra initialization here

    //向列表框内添加应用程序名称

    m_ListProcs.AddString(_T("control"));  //打开控制面板

    m_ListProcs.AddString(_T("pword"));  //打开写字板

    m_ListProcs.AddString(_T("cmd"));        //打开命令行 

    m_ListProcs.AddString(_T("iesample"));  //注意在WINCE7中为iesample,而不是ieplore

    m_ListProcs.SetCurSel(0);

    return TRUE;  // return TRUE  unless you set the focus to a control

    }

    然后实现”Create a Process”按钮的单击事件处理函数:

    void CCreateProcessDlg::OnBnClickedButtonCreateproc()

    {

    // TODO: Add your control notification handler code here

    CString procName;

    m_ListProcs.GetText(m_ListProcs.GetCurSel(),procName);

    procName.Format(_T("\\Windows\\%s.exe"),procName);

    AfxMessageBox(procName);

    PROCESS_INFORMATION processInfo; //

        if (!CreateProcess(procName, NULL, NULL, NULL, NULL

          , CREATE_NEW_CONSOLE

          , NULL, NULL, NULL, &processInfo))

        {

    MessageBox(_T("Failed to create a new process"));

    return;

    }

        CloseHandle(processInfo.hThread);

    CloseHandle(processInfo.hProcess);

    }

    这里在创建进程时运行指定的应用程序,在运行之前将应用程序的完整路径补上。调用CreateProcess()函数后,我们需要检查返回值,如果创建进程成功,那么立即关闭返回的进程句柄和主线程句柄,因为我们不需要用到它们。

    6 1.3 终止进程

    进程都是有生命周期,当进程完成所有的操作后,从主线程的入口函数返回之后,这个进程就会被终止,这是一种比较理想的终止进程的方式,这样能够保证主线程的资源被正确清理。除了这个常规的终止进程的方式,下面介绍几种“非常规”的终止进程的方式:

    1) 进程中的某一个线程(不一定是主线程)调用ExitProcess()函数。这个函数的原型如下:

    VOID ExitProcess(

      UINT uExitCode

    );

    参数uExitCode指定进程同时也是它的所有线程的退出码,可以分别通过GetExitCodeProcess()GetExitCodeThread()函数获取指定进程及线程的退出码。

    2) 一个进程调用TerminateProcess()函数终止另一个进程,当然这个首先必须获取被终止进程的句柄,比如我们上面介绍的CreateProcess()函数就会向父进程返回新创建子进程的句柄。TerminateProcess()函数的原型如下:

    BOOL TerminateProcess(

      HANDLE hProcess,

      DWORD uExitCode

    );

    参数hProcess指定将被终止进程的句柄,实际上如果知道进程的ProcessId就可以通过调用OpenProcess()函数获取进程的句柄。参数uExitCode指定进程以及它的所有线程的退出码。

    如果终止进程成功,将返回非0;如果失败就返回0。如果系统非常忙,那么TerminateProcess()可能会由于超时而失败,这时GetLastError将返回ERROR_BUSY

    3) 终止进程的主线程。一旦主线程被终止,那么进程以及进程包含的所有线程都会被终止,终止主线程的方法有:调用ExitThread()函数或调用TerminateThread()函数。

    有一点需要注意的是终止一个进程并不总意味着系统将销毁进程的内核对象,只有当进程内核对象的引用计数都为0时,才会被删除。

    6.1.4 其他相关函数

    除了上面介绍的几个用于创建,终止进程的函数外,Windows CE7还提供了其它的几个操作进程的函数,下面分别介绍。

    l GetCommandLine()函数返回当前进程的命令行字符串

    l GetCurrentProcess()函数返回当前进程的句柄,当进程内部需要用到自己的句柄时,这个函数就比较有用

    l GetCurrentProcessId()函数返回当前进程的标识ProcessId

    l GetExitCodeProcess()函数获取指定进程的终止状态,这个函数的原型如下:

    BOOL GetExitCodeProcess(

      HANDLE hProcess,

      LPDWORD lpExitCode

    );

    参数hProcess指定进程的句柄,参数lpExitCode用于返回进程的终止状态。如果指定的进程还没有被终止,那么进程的终止状态将返回STILL_ACTIVE,这可以用于判断一个进程是否存活。如果进程已经终止,那么退出状态可以是如下的几种值:(1)如果进程被“非常规”终止,此时将是在上面介绍过的ExitProcess()TerminateProcess()函数内指定的进程的退出码;(2)如果进程从主线程的入口函数 (mainWinMain) 正常返回,此时将是主线程入口函数的返回值(return value)(3)如果进程由于未处理的异常而被终止,此时将是异常值。

    如果获取进程的终止状态成功,返回非0,否则返回0

    l OpenProcess()函数用于获取指定进程对象的句柄,这个函数的原型如下:

    HANDLE OpenProcess(

      DWORD fdwAccess,

      BOOL fInherit,

      DWORD IDProcess

    );

    参数fdwAccess指定对进程对象的访问权限,这个在Widows CE中不支持,设置为0;参数fInherit指定是否继承句柄,Windows CE中也不支持,设置为FALSE;参数IDProcess指定进程的标识ProcessIdOpenProcess()函数使用进程的ProcessId获取进程句柄,如果失败,将返回NULL

    l ReadProcessMemory()用于读取指定进程的地址空间,函数原型如下:

    BOOL ReadProcessMemory(

      HANDLE hProcess,

      LPCVOID lpBaseAddress,

      LPVOID lpBuffer,

      DWORD nSize,

      LPDWORD lpNumberOfBytesRead

    );

    参数hProcess指定被访问进程的句柄;参数lpBaseAddress指定访问的起始地址;参数lpBuffer用于接收从进程内读出的数据;参数nSize指定读取的数据量,单位为字节;参数lpNumberOfBytesRead返回实际读出的数据量,单位为字节,这个可以设置为NULL,表示不关心实际读回的数据量。如果从指定进程读回数据成功,函数返回非0,否则将返回0

    l WriteProcessMemory()函数则用于向进程空间内写入数据,这个函数的原型如下:

    BOOL WriteProcessMemory(

      HANDLE hProcess,

      LPVOID lpBaseAddress,

      LPVOID lpBuffer,

      DWORD nSize,

      LPDWORD lpNumberOfBytesWritten

    );

    这几个参数的涵义与ReadProcessMemory类似。

    6.2 Windows Embedded Compact 7中的线程

    6.2.1 线程概述

    Windows CE是支持优先级的多任务操作系统,允许一个进程创建一个或多个线程。线程是进程中的一个实体,是系统独立调度以及分配CPU的基本单位。同一个进程内的所有线程共享进程的资源,包括进程的地址空间,每个线程对句柄都拥有相同的访问权限,包括同步对象句柄,文件句柄以及内存对象句柄等。每个线程都具有一个独立的栈,包含各自的CPU寄存器状态,这些也称之为线程的上下文。当线程被调度换出时,需要将这些上下文信息保存到线程状态中;当线程被调度执行时,再将上下文信息恢复,继续往下执行。

    Windows CE中的线程具有多种状态,可以为:运行(Running),挂起(Suspend),睡眠(Sleeping),阻塞(Blocked),终止(Terminated)。当所有线程都处于阻塞状态时,Windows CE将进入空闲模式(Idle mode),这时CPU消耗的能耗将很小。

    线程的优先级

    Windows CE采用一种基于优先级的时间片调度算法来调度线程的执行,每个线程都被分配一个优先级,由于Windows CE不支持进程的优先类别,线程的优先级不受进程的影响,可以直接设置线程的优先级,优先级比较高的线程总是比优先级低的线程优先被调度,而不像Windows桌面系统那样设置线程在进程内的相对优先级。一个线程每次被调度执行最多占用CPU一个时间片,在线程的时间片用完之后,如果该线程不是一个运行到完成类型的线程,那么这个线程将被挂起,调度另一个线程运行。如果线程提前完成任务,可以请求放弃自己的时间片。

    Windows CE 7中,可以支持256种不同的线程优先级,取值范围为[0, 255],值越小表示优先级越高,其中0代表最高优先级,255代表最低优先级。前面248个优先级(0 ~ 247)代表实时优先级一般用于设备驱动或系统级线程,应用程序一般使用最低的8个优先级(248 ~ 255),表6-2列出了这些优先级。

    6-2 Windows CE中应用程序使用的线程优先级

    优先级

    说明

    THREAD_PRIORITY_TIME_CRITICAL

    比正常优先级高3

    THREAD_PRIORITY_HIGHEST

    比正常优先级高2

    THREAD_PRIORITY_ABOVE_NORMAL

    比正常优先级高1

    THREAD_PRIORITY_NORMAL

    正常优先级,值为251,创建线程时使用的默认优先级

    THREAD_PRIORITY_BELOW_NORMAL

    比正常优先级低1

    THREAD_PRIORITY_LOWEST

    比正常优先级低2

    THREAD_PRIORITY_ABOVE_IDLE

    比正常优先级低3

    THREAD_PRIORITY_IDLE

    比正常优先级低4

    Windows CE中,调度程序不使用任何老化算法,这就意味着高优先级的线程总是比低优先级的线程优先被调度,因此可能出现低优先级线程饥饿的现象(长时间不被调度),所以用户需要谨慎设置线程的优先级。相同优先级的线程采用轮换(Round-Robin)算法来调度,一旦线程时间片用完或者因为等待资源而被阻塞,都会被唤出。只有当优先级较高的线程都被阻塞的时候,一个优先级比较低的线程才能被调度执行,这听起来好像比较低效,但其实这是针对Windows CE的特殊环境而设计的,因为线程在多数情况下总是被阻塞的,为了等待一些资源(如等待同步对象)

    另外Windows CE采用抢占式的调度策略,就是说一旦系统新创建一个更高优先级的线程或者某个更高优先级的线程从阻塞中返回,那么调度器会立即将当前的低优先级的线程挂起,调度高优先级的线程运行。

    在大多数情况下,线程的优先级是保持不变的(没有老化),除非用户进行显示设置。但是有一个例外,当一个低优先级的线程占有一个高优先级等待请求的资源,那么内核就会通过优先级继承的方式临时将低优先级的线程继承高优先级线程的优先级,从而保证它以较高的优先级运行,直到它释放所占用的资源,才恢复回它原来的优先级,这样高优先级线程就能抢占执行,这称为优先级反转。

    6.2.2 创建线程

    Windows CE中,创建线程的函数是CreateThread(),线程将在调用进程的地址空间内运行,这个函数的原型如下:

    HANDLE CreateThread(

      LPSECURITY_ATTRIBUTES lpsa,

      DWORD cbStack,

      LPTHREAD_START_ROUTINE lpStartAddr,

      LPVOID lpvThreadParam,

      DWORD fdwCreate,

      LPDWORD lpIDThread

    );

    l 参数lpsa指定线程的安全属性,在Windows CE中不支持,必须设置为NULL

    l 参数cbStack指定为新创建线程保留的虚拟内存大小,这个参数实际上指定了线程栈能够增长的最大空间。参数cbStack只有在当fdwCreate参数的STACK_SIZE_PARAM_IS_A_RESERVATION标记被设置时才有效;否则将被忽略,此时线程的将使用默认的栈大小。

    l 参数lpStartAddr指定线程启动函数的入口地址。

    l 参数lpvThreadParam为指向传递给线程函数的参数的指针,可以传递任意类型的参数,一般的用法是将线程函数需要的多个参数放到一个自定义的结构体内,再将lpvThreadParam赋值为指向参数结构体的指针。

    l 参数fdwCreate指定创建线程的标记,可以取的值为0CREATE_SUSPENDED和STACK_SIZE_PARAM_IS_A_RESERVATION。如果CREATE_SUSPENDED标记被设置,那么线程将被创建为挂起状态,直到对应该线程的ResumeThread()函数被调用,这个线程才能恢复运行;否则线程将在被创建好后直接运行。默认为新创建线程保留的栈大小为64KB,如果用户希望增加线程可用的最大栈大小,就设置STACK_SIZE_PARAM_IS_A_RESERVATION标记,并通过cbStack参数指定栈的大小。

    l 参数lpIDThread为输出参数,用于返回新创建线程的标识(ID)。如果用户不需要获取线程标识,可以将这个参数设置为NULL

    如果创建线程成功,这个函数将返回新创建线程的句柄,否则返回NULL。返回的线程句柄被创建为THREAD_ALL_ACCESS,使用这个句柄可以对线程进行操作。一旦不再使用线程句柄,就应该将其关闭。

    新创建的线程从传递进来的线程函数的入口地址开始执行(属于进程的地址空间),可以认为线程函数用于指定任务,线程函数的原型必须为这样:

    DWORD WINAPI ThreadProc(

      LPVOID lpParameter

    );

    l 参数lpParameter为指向传递给线程函数的参数的指针。注意不要给线程函数传递一个分配在栈上的结构体的指针,因为栈上的内容会在CreateThread()返回后被回收,导致线程无法访问。

    对线程函数,返回非0表示执行成功,返回0表示失败。通过调用GetExitCodeThread()函数可以获取线程函数的返回值(如果其作为线程的退出码),函数原型如下:

    BOOL GetExitCodeThread(

      HANDLE hThread,

      LPDWORD lpExitCode

    );

    l 参数hThread指定线程的句柄。

    l 参数lpExitCode为输出参数,返回线程的退出码。

    如果函数调用成功,返回非0(TRUE),否则返回0(FALSE)。如果线程仍在运行中,还没有终止,那么lpExitCode中将返回STILL_ACTIVE。如果线程已经终止,线程的退出码可能为如下情况:(1)如果线程是被ExitThread()TerminateThread()终止,将为这两个函数指定的退出值; (2)如果线程由于线程函数返回被终止,为线程函数的返回值;(3)如果线程由于所属进程被终止而被终止,那么此时为进程的退出码。

    从线程退出码的情况,读者应该能够理解线程被终止的方式,下面分别介绍ExitThread()TerminateThread()函数。ExitThread()函数用于在线程内部终止自己,它的原型分别为:

    VOID ExitThread(

      DWORD dwExitCode

    );

    l 参数dwExitCode指定线程的退出码。

    TerminateThread()函数用于终止其它指定的线程,函数原型如下:

    BOOL TerminateThread(

      HANDLE hThread,

      DWORD dwExitCode

    );

    l 参数hThread指定线程的句柄。

    l 参数dwExitCode指定将被终止线程的退出码。

    如果终止线程成功,返回非0,否则返回0TerminateThread()是一个比较危险的函数,因为在目标线程退出时无法执行用户态的代码(比如析构函数),只有在极端情况下才使用。

    如果在创建线程时指定一个无效的入口地址,将导致抛出异常,线程将被终止。

    6 2.3 设置线程的优先级

    Windows CE中,设置一般程序的线程优先级使用SetThreadPriority()函数:

    BOOL SetThreadPriority(

      HANDLE hThread,

      int nPriority

    );

    l 参数hThread指定线程句柄

    l 参数nPriority指定设置的线程优先级值,可以表6-2列出的值之一。

    如果设置线程优先级成功,函数返回非0,否则返回0。在Windows CE中默认新创建线程的优先级为THREAD_PRIORITY_NORMAL(251),不到万不得已,不要随意更改线程的优先级,避免高优先级的线程总是占用CPU事件,导致系统响应变得迟缓。

    而CeSetThreadPriority()函数则用于设置实时线程的优先级:

    BOOL CeSetThreadPriority(

      HANDLE hThread,

      int nPriority

    );

    这里的参数nPriority可以取0 ~ 255中的任意值,Windows CE还提供了下面的几个常量优先级可供使用,注意不要跟表6-2中的值混淆:

    · CE_THREAD_PRIO_256_TIME_CRITICAL

    · CE_THREAD_PRIO_256_HIGHEST

    · CE_THREAD_PRIO_256_ABOVE_NORMAL

    · CE_THREAD_PRIO_256_NORMAL

    · CE_THREAD_PRIO_256_BELOW_NORMAL

    · CE_THREAD_PRIO_256_LOWEST

    · CE_THREAD_PRIO_256_ABOVE_IDLE

    · CE_THREAD_PRIO_256_IDLE

    如果设置优先级的线程当前正在被阻塞,那么提高优先级,将马上生效,而降低优先级只能等到线程从阻塞中返回后才生效。

    下面的例子演示如何创建多个线程,并设置线程的优先级,程序的运行界面如图6.2所示。

    在这个程序中,我们将创建4个线程,左边的4个组合框用于选择每个线程的优先级,这里我们只提供NormalBelow_Normal两种优先级,切记谨慎将线程优先级设置成Normal之上,很容易导致系统无法响应。右边按钮”Start”用于启动线程,”Stop”按钮终止线程的执行。这里的线程函数很简单,不断对一个计数器加一,通过计数器最终的值,我们能够推断出线程被调度的频次。

    6.2 设置线程优先级的运行结果

    对话框类的头文件为:

    // SetThreadPriorityDlg.h : header file

    //

    #pragma once

    #include "afxwin.h"

    // CSetThreadPriorityDlg dialog

    class CSetThreadPriorityDlg : public CDialog

    {

    // Construction

    public:

    CSetThreadPriorityDlg(CWnd* pParent = NULL); // standard constructor

    // Dialog Data

    enum { IDD = IDD_SETTHREADPRIORITY_DIALOG };

    protected:

    virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

    // Implementation

    protected:

    HICON m_hIcon;

    // Generated message map functions

    virtual BOOL OnInitDialog();

    #if defined(_DEVICE_RESOLUTION_AWARE) && !defined(WIN32_PLATFORM_WFSP)

    afx_msg void OnSize(UINT /*nType*/, int /*cx*/, int /*cy*/);

    #endif

    DECLARE_MESSAGE_MAP()

    public:

    CComboBox m_ComboThread1;

    CComboBox m_ComboThread2;

    CComboBox m_ComboThread3;

    CComboBox m_ComboThread4;

    CButton m_ButtonStart;

    CButton m_ButtonStop;

    afx_msg void OnBnClickedButtonStart();

    afx_msg void OnBnClickedButtonStop();

    static DWORD ThreadProc(PVOID pArg);

    protected:

    int m_threadID[4];

    HANDLE m_hThread[4];

    int m_ListThreadPriorities[3];

    public:

    CEdit m_EditOutput;

    afx_msg void OnCbnSelchangeComboThread1();

    };

    其中线程函数为ThreadProc(),这里设置为静态函数:

    public:\

      static DWORD ThreadProc(PVOID pArg);

    添加三个辅助变量:m_threadID为线程的ID数组,通过将其传入线程函数,实现每个线程增加对应的计数器;m_hThread用于存放新建的4个线程的句柄;m_ListThreadPriorities存放线程的优先级,根据用户的选择来分别设置每个线程的优先级。

    protected:

    int m_threadID[4];

    HANDLE m_hThread[4];

    int m_ListThreadPriorities[3];

    接着在对话框类的开始位置定义连个全局变量:g_stopRunning为标识,如果这个值被设置为FALSE,那么线程将退出,后面我们将看到,线程函数使用这个标识作为判断是否循环结束;g_threadCount为每个线程的计数值,值越大,说明线程被调度得越多。

    bool g_stopRunning;

    __int64 g_threadCount[4];

    在对话框的初始化函数中为每个组合框进行初始化:

    BOOL CSetThreadPriorityDlg::OnInitDialog()

    {

    CDialog::OnInitDialog();

    // Set the icon for this dialog.  The framework does this automatically

    //  when the application's main window is not a dialog

    SetIcon(m_hIcon, TRUE); // Set big icon

    SetIcon(m_hIcon, FALSE); // Set small icon

    // TODO: Add extra initialization here

    //添加线程优先级选项

    //注意:这里如果允许设置Above_Normal优先级,系统将很可能进入无法响应状态

    m_ComboThread1.AddString(_T("Normal"));  //Normal优先级

    m_ComboThread1.AddString(_T("Below_Normal")); //低于Normal

    //m_ComboThread1.AddString(_T("Above_Normal")); //高于Normal

    m_ComboThread1.SetCurSel(0);

    m_ComboThread2.AddString(_T("Normal"));  //Normal优先级

    m_ComboThread2.AddString(_T("Below_Normal")); //低于Normal

    //m_ComboThread2.AddString(_T("Above_Normal")); //高于Normal

    m_ComboThread2.SetCurSel(0);

    m_ComboThread3.AddString(_T("Normal"));  //Normal优先级

    m_ComboThread3.AddString(_T("Below_Normal")); //低于Normal

    //m_ComboThread3.AddString(_T("Above_Normal")); //高于Normal

    m_ComboThread3.SetCurSel(0);

    m_ComboThread4.AddString(_T("Normal"));  //Normal优先级

    m_ComboThread4.AddString(_T("Below_Normal")); //低于Normal

    //m_ComboThread4.AddString(_T("Above_Normal")); //高于Normal

    m_ComboThread4.SetCurSel(0);

    //辅助线程优先级数组,根据列表框的下标快速获取优先级值

    m_ListThreadPriorities[0] = THREAD_PRIORITY_NORMAL;

    m_ListThreadPriorities[1] = THREAD_PRIORITY_BELOW_NORMAL;

    //m_ListThreadPriorities[2] = THREAD_PRIORITY_ABOVE_NORMAL;

    m_ButtonStart.EnableWindow();

    m_ButtonStop.EnableWindow(false);

    g_stopRunning = false;

    int i;

    for(i = 0; i < 4; i++)

    {

    g_threadCount[i] = 0;

    m_threadID[i] = i;

    }

    return TRUE;  // return TRUE  unless you set the focus to a control

    }

    线程函数的实现比较简单,判断g_stopRunning是否为TRUE,如果是,线程退出;否则不断增加自己负责的计数值:

    DWORD CSetThreadPriorityDlg::ThreadProc(PVOID pArg)

    {

    //获取线程标识,用于更新指定下标的计数

    int tid = *((int*)pArg);

    //不断计数,直到线程停止标识被设置为true

    while(!g_stopRunning)

    {

    g_threadCount[tid]++;

    }

    return tid;

    }

    下面是”Start”按钮的单击事件处理函数,首先将g_stopRunning设置为false,然后创建线程:

    void CSetThreadPriorityDlg::OnBnClickedButtonStart()

    {

    // TODO: Add your control notification handler code here

    //禁止Start按钮,启用Stop按钮

    m_ButtonStart.EnableWindow(false);

    m_ButtonStop.EnableWindow(true);

    //设置终止线程运行标识为false

    g_stopRunning = false;

    //初始化每个线程对应的计数器为0

    int i;

    for(i = 0; i < 4; i++)

    {

    g_threadCount[i] = 0;

    }

    int successThreadNum = 0;

    //创建4个线程,分别开始计数

    for( i = 0; i < 4; i++ )

    {

    m_hThread[i] = CreateThread(

    NULL,                    //不支持安全属性

    0,                       //线程使用默认栈大小

    ThreadProc,              //线程入口函数

    &m_threadID[i],          //传递给线程的参数,这里指定为操作的下标

    CREATE_SUSPENDED,        //挂起线程,将在设置线程优先级之后,再启动线程

    NULL);                   //不需要获取线程标识

    if(!m_hThread[i])

    {

    CString failedMsg;

    failedMsg.Format(_T("Thread %d create failed"),i);

    AfxMessageBox(failedMsg);

    }

    else{

    successThreadNum++;

    }

    }

    //为每个线程设置指定的优先级,需要判断线程是否创建成功

    if(m_hThread[0]) 

    {

    SetThreadPriority(m_hThread[0], m_ListThreadPriorities[m_ComboThread1.GetCurSel()]);

    }

    if(m_hThread[1]) 

    {

    SetThreadPriority(m_hThread[1], m_ListThreadPriorities[m_ComboThread2.GetCurSel()]);

    }

    if(m_hThread[2]) 

    {

    SetThreadPriority(m_hThread[2], m_ListThreadPriorities[m_ComboThread3.GetCurSel()]);

    }

    if(m_hThread[3]) 

    {

    SetThreadPriority(m_hThread[3], m_ListThreadPriorities[m_ComboThread4.GetCurSel()]);

    }

    //正式启动每个线程开始运行

    for( i = 0; i < 4; i++ )

    {

    if(m_hThread[i])

    {

    ResumeThread(m_hThread[i]);

    }

    }

    CString runMsg;

    runMsg.Format(_T("%d threads running..."),successThreadNum);

    m_EditOutput.SetWindowTextW(runMsg);

    }

    注意在调用CreateThread()函数创建线程时,我们指定为挂起状态,这样保证线程从开始就以指定的优先级运行:

    m_hThread[i] = CreateThread(

    NULL,                    //不支持安全属性

    0,                       //线程使用默认栈大小

    ThreadProc,              //线程入口函数

    &m_threadID[i],          //传递给线程的参数,这里指定为操作的下标

    CREATE_SUSPENDED,        //挂起线程,将在设置线程优先级之后,再启动线程

    NULL);                   //不需要获取线程标识

    接着就可以调用SetThreadPriority()函数为每个线程设置优先级,为了防止线程创建失败,我们都进行判断线程句柄是否创建成功,如对0号线程:

    if(m_hThread[0]) 

    {

    SetThreadPriority(m_hThread[0], 

    m_ListThreadPriorities[m_ComboThread1.GetCurSel()]);

    }

    设置好线程的优先级属性后就可以启动线程了,调用ResumeThread()函数实现:

    //正式启动每个线程开始运行

    for( i = 0; i < 4; i++ )

    {

    if(m_hThread[i])

    {

    ResumeThread(m_hThread[i]);

    }

    }

    “Stop”按钮负责终止线程的执行,这通过将g_stopRunning设置为true实现,然后将每个线程的计数值输出到右下方的文本框中,最后记住调用CloseHandle()关闭每个线程句柄:

    void CSetThreadPriorityDlg::OnBnClickedButtonStop()

    {

    // TODO: Add your control notification handler code here

    g_stopRunning = true;  //终止线程运行

    Sleep(50); //等待线程结束

    //格式化输出每个不同优先级线程的计数值

    CString result = _T("");

    int i;

    for( i = 0; i < 4; i++)

    {

    result.Format(_T("%s thread %d count=%I64d;\r\n"),result, i, g_threadCount[i]);

    }

    m_EditOutput.SetWindowTextW(result);

    //关闭线程句柄

    for( i = 0; i < 4; i++)

    {

    CloseHandle(m_hThread[i]);

    }

    //设置按钮状态

    m_ButtonStart.EnableWindow();

    m_ButtonStop.EnableWindow(false);

    }

    6.2演示的是4个线程都设置成Normal优先级的运行结果,可以看到,四个线程的计数器值几乎一样,说明它们的调度频次是一样的。而图6.3则演示将其中两个线程设置成Normal,另外两个线程设置成Below_Normal的运行结果。可以看到,设置成Below_Normal的线程的计数器为0,说明它们从没有被调度执行过,而两个Normal线程的计数器值几乎相等。

    6.3 线程设置成不同优先级的运行结果

    6.2.4 查询线程的优先级别

    与设置线程优先级对应,有两个不同的查询线程优先级的方式:GetThreadPriority()函数用于查询一般线程的基本优先级,CeGetThreadPriority()用于查询实时线程的优先级。

    int GetThreadPriority(

      HANDLE hThread

    );

    int CeGetThreadPriority(

      HANDLE hThread

    );

    参数hThread指定线程的优先级。如果查询线程优先级成功,函数返回指定线程的优先级,如果失败将返回THREAD_PRIORITY_ERROR_RETURN,通过调用GetLastError()函数进一步获取错误信息。如果线程因为优先级反转而导致优先级临时变化,那么此时查询得到的优先级将与之前默认得到或设置的优先级别不同。

    展开全文
  • 进程(Process)是具有一定独立功能程序关于某个数据集合上一次运行活动,是系统进行资源分配和调度一个独立单位。程序只是一组指令有序集合,它本身没有任何运行含义,只是一个静态实体。而进程则不同,...
  • 线程是CPU分配时间的单位,每一个线程对应于它在进程中的一个函数,也就是内存中的代码段,多个线程执行时CPU会根据它们优先级分配时间,使它们完成自己功能。 一般来说,进程至少一个线程,一个主线程和其他...
    进程是系统分配资源的单位,每一个进程对应与一个活动的程序,当进程激活时,操作系统就将系统的资源包括内存、I/O和CPU等分配给它,使它执行。  
      线程是CPU分配时间的单位,每一个线程对应于它在进程中的一个函数,也就是内存中的代码段,多个线程执行时CPU会根据它们的优先级分配时间,使它们完成自己的功能。  
      一般来说,进程中至少一个线程,一个主线程和其他线程组成一个进程。多个线程的目的在于分享CPU的时间片,从而完成并行任务。
    下面是自己整理的:
     线程和进程的比较:
    线程是比进程更小的能独立运行的基本单位,通常一个进程都有若干个线程,至少也需要一个线程。
    1.调度
    线程师调度和分派的基本单位,进程是资源拥有的基本单位。
    2.并发性
    进程之间可以并发执行,在一个进程中的多个线程之间也可以并发执行。
    3.拥有资源
    进程是拥有资源的一个独立单元,线程自己不拥有系统资源(也有一点比不可少的资源)但它可以访问其隶属进程的资源。
    4.系统开销
    创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、I/O设备等,OS所付出的开销显著大于在创建或撤消线程时的开销;进程切换的开销也远大于线程切换的开销。

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

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

      以沙箱为例进行阐述。一个进程就好比一个沙箱。线程就如同沙箱中的孩子们。孩子们在沙箱子中跑来跑去,并且可能将沙子攘到别的孩子眼中,他们会互相踢打或撕咬。但是,这些沙箱略有不同之处就在于每个沙箱完全由墙壁
    和顶棚封闭起来,无论箱中的孩子如何狠命地攘沙,他们也不会影响到其它沙箱中的其他孩子。因此,每个进程就象一个被保护起来的沙箱。未经许可,无人可以进出。

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

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

      第四列,CPU时间,是CPU被进程中的线程累计占用的小时、分钟及秒数。请注意,我对进程中的线程使用占用一词。这并不一定意味着那就是进程已耗用的CPU时间总和,因为,如我们一会儿将看到的,NT计时的方式是,当特定的时钟间隔激发时,无论谁恰巧处于当前的线程中,它都将计算到CPU周期之内。通常情况下,在大多数NT系统中,时钟以10毫秒的间隔运行。每10毫秒NT的心脏就跳动一下。有一些驱动程序代码片段运行并显示谁是当前的线程。让我们将CPU时间的最后10毫秒记在它的帐上。因此,如果一个线程开始运行,并在持续运行8毫秒后完成,接着,第二个线程开始运行并持续了2毫秒,这时,时钟激发,请猜一猜这整整10毫秒的时钟周期到底记在了哪个线程的帐上?答案是第二个线程。因此,NT中存在一些固有的不准确性,而NT恰是以这种方式进行计时,实际情况也如是,大多数32位操作系统中都存在一个基于间隔的计时机制。请记住这一点,因为,有时当你观察线程所耗用的CPU总和时,会出现尽管该线程或许看上去已运行过数十万次,但其CPU时间占用量却可能是零或非常短暂的现象,那么,上述解释便是原因所在。上述也就是我们在任务管理器的进程选项卡中所能看到的基本信息列。

     


    关于进程,线程之间的通信方式:

    转载于:https://www.cnblogs.com/sideandside/archive/2007/04/04/699637.html

    展开全文
  • 线程(Thread)是进程一个实体,是CPU调度和分派基本单位。线程不能够独立执行,必须依存在应用程序,由应用程序提供多个线程执行控制。 进程有独立地址空间,一个进程崩溃后,在保护模式下不会对其它进程...

    C# 与Thread,与计算机线程,进程本质关系

     线程进程本质:
    进程是计算机操作系统分配资源的单位,线程可与同一线程共享全部资源
    进程和进程之间,通信很困难,(分布式)
    线程是进程的实体,不能单独存在,依托程序存在,他是CPU调度分配的基本单位

    同一时间,允许两个或以上进程处于运行状态,就是多任务,但是它是基于时间片轮转进程调度算法,
    一个电脑cpu,理论上同一时间,有且仅有一个进程cpu

    进程和线程是基于操作系统,和计算机,程序运行在计算机的操作系统上,
    因此不是程序,创造出线程,它只是借助Thread对线程进行封装,进而调用线程,

    前言:

    进程是程序在计算机上的一次执行活动。当 你运行一个程序,你就启动了一个进程。

    进程是操作系统分配资源的单位。在Windows下,进程又被细化为线程,也就是一个进程下有多个能独立运行的更小的单位。

    线程(Thread)是进程的一个实体,是CPU调度和分派的基本单位。线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

    进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个 进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序 健壮

    但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

     

    就像进程一样,Linux 使用 task_struct 结构体描述和记录线程,每个线程都有唯一属于自己的 task_struct 结构。从这个角度来看,线程就是一个普通的进程,只不过线程可能和其他进程共享一些资源而已。

    以 Windows 为代表的一些操作系统提供了专门用于创建线程的机制,在这些系统中,线程常常被称作“轻量级进程”,因为相对于进程而言,线程耗费的资源较少,能够较为迅速的创建和投入运行。

    但是对于 Linux 而言,线程不过是进程之间共享资源的一种手段罢了。那么是不是 Linux 中的线程比 Windows 中的线程更加“重量级”呢?也不是,因为 Linux 中的进程本身就很轻量级,Linux 创建进程所需时间,并不比 Windows 创建线程所需时间多多少。

    从C语言代码层面来看,假设某个进程包含 4 个线程,以 Windows 为代表的一些操作系统一般会有一个包含指向 4 个不同线程的指针的进程描述符,负责描述地址空间、打开的文件等共享资源,而线程本身再去描述自己独占的资源。

    正文:

    进 程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个 进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序 健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

    1) 简而言之,一个程序至少有一个进程,一个进程至少有一个线程.

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

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

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

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

     

    进程就是一个程序运行的时候被CPU抽象出来的,一个程序运行后被抽象为一个进程,但是线程是从一个进程里面分割出来的,由于CPU处理进程的时候是采用时间片轮转的方式,所以要把一个大个进程给分割成多个线程,例如:网际快车中文件分成100部分 10个线程 文件就被分成了10份来同时下载 1-10 占一个线程 11-20占一个线程,依次类推,线程越多,文件就被分的越多,同时下载 当然速度也就越快

    进程是程序在计算机上的一次执行活动。当 你运行一个程序,你就启动了一个进程。显然,程序只是一组指令的有序集合,它本身没有任何运行的含义,只是一个静态实体。而进程则不同,它是程序在某个数 据集上的执行,是一个动态实体。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消,反映了一个程序在一定的数据集上 运行的全部动态过程。进程是操作系统分配资源的单位。在Windows下,进程又被细化为线程,也就是一个进程下有多个能独立运行的更小的单位。线程(Thread)是进程的一个实体,是CPU调度和分派的基本单位。线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。

    线程和进程的关系是:线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。

    在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态,这便是多任务。现代的操作系统几乎都是多任务操作系统,能够同时管理多个进程的运行。 多任务带来的好处是明显的,比如你可以边听mp3边上网,与此同时甚至可以将下载的文档打印出来,而这些任务之间丝毫不会相互干扰。那么这里就涉及到并行的问题,俗话说,一心不能二用,这对计算机也一样,原则上一个CPU只能分配给一个进程,以便运行这个进程。我们通常使用的计算机中只有一个CPU,也就是说只有一颗心,要让它一心多用,同时运行多个进程,就必须使用并发技术。实现并发技术相当复杂,最容易理解的是“时间片轮转进程调度算法”,它的思想简单介绍如下:在操作系统的管理下,所有正在运行的进程轮流使用CPU,每个进程允许占用CPU的时间非常短(比如10毫秒),这样用户根本感觉不出来CPU是在轮流为多个进程服务,就好象所有的进程都在不间断地运行一样。但实际上在任何一个时间内有且仅有一个进程占有CPU。

    如果一台计算机有多个CPU,情况就不同了,如果进程数小于CPU数,则不同的进程可以分配给不同的CPU来运行,这样,多个进程就是真正同时运行的,这便是并行。但如果进程数大于CPU数,则仍然需要使用并发技术。

     

    Linux并不特殊对待线程,在Linux看来,线程不过就是一种特殊的进程而已。那么,Linux是如何创建线程的呢?

    线程机制是大多数现代编程语言都会提供的机制,该机制允许在同一进程的共享内存地址空间运行一组“特殊的进程(即线程)”。这些线程不仅共享同一段内存空间,还可以共享已经打开的文件,统计量等其他资源。线程机制支持程序并发运行,在多处理器核心的系统上,该并发机制能够实现多条线程同时运行。

    Linux 管理线程的方式不同于其他一些经典操作系统,Linux 并没有线程的概念,它把线程当作进程的一个子集来管理。因此,Linux 内核并未为线程提供额外调度算法,也没有提供额外的数据结构用于描述和存储线程。

    Linux 并没有线程的概念

    就像进程一样,Linux 使用 task_struct 结构体描述和记录线程,每个线程都有唯一属于自己的 task_struct 结构。从这个角度来看,线程就是一个普通的进程,只不过线程可能和其他进程共享一些资源而已。

    以 Windows 为代表的一些操作系统提供了专门用于创建线程的机制,在这些系统中,线程常常被称作“轻量级进程”,因为相对于进程而言,线程耗费的资源较少,能够较为迅速的创建和投入运行。

    但是对于 Linux 而言,线程不过是进程之间共享资源的一种手段罢了。那么是不是 Linux 中的线程比 Windows 中的线程更加“重量级”呢?也不是,因为 Linux 中的进程本身就很轻量级,Linux 创建进程所需时间,并不比 Windows 创建线程所需时间多多少。

    从C语言代码层面来看,假设某个进程包含 4 个线程,以 Windows 为代表的一些操作系统一般会有一个包含指向 4 个不同线程的指针的进程描述符,负责描述地址空间、打开的文件等共享资源,而线程本身再去描述自己独占的资源。

    仅仅是将有收获的文字,重点贴出来,当做收藏笔记,参考出处:

    https://my.oschina.net/cnyinlinux/blog/367910

    https://baijiahao.baidu.com/s?id=1641266309400219826&wfr=spider&for=pc

    展开全文
  • Java中的线程

    2021-04-15 22:28:41
    比如在Windows系统,一个运行exe就是一个进程。 是系统进行资源分配和调度独立单位 每一个进程都有它自己内存空间和系统资源 线程: 是进程中的单个顺序控制流,是一条执行路径。 线程总是属于某个...

    Java中的线程

    1. 引入

    1.1. 进程和线程

    1. 进程

      1. 是正在运行的程序。比如在Windows系统中,一个运行的exe就是一个进程。
      2. 是系统进行资源分配和调度的独立单位
      3. 每一个进程都有它自己的内存空间和系统资源
      4. 在这里插入图片描述
    2. 线程:

      1. 是进程中的单个顺序控制流,是一条执行的路径。
      2. 线程总是属于某个进程,线程没有自己的虚拟地址空间,与进程内的其他线程一起共享分配给该进程的所有资源。
      3. 是CPU调度和分派的最小单位。
      4. 一个程序至少有一个进程,一个进程至少有一个线程。

    1.2. 单线程和多线程

    1. 单线程:一个进程如果只有一条执行路径,则称为单线程程序
      1. 单线程程序演示
    2. 多线程:一个进程如果有多条执行路径,则称为多线程程序
      1. 多线程程序演示

    注:在线程之间实际上是”轮流“执行的,而并非是”同时“执行的。

    2. Java中的线程——Thread

    2.1. 涉及总览

    • Java中的线程基础内容讲解
    • Java中线程的两种创建方式
      • Thread 类进行派生并覆盖 run方法
      • 实现Runnable接口创建线程
    • 线程优先级
    • 线程生存周期【重点理解】
    • Java中的多线程在实际中的应用----卖票案例
    • 卖票案例中存在的问题分析
    • 同步代码块解决多线程中数据安全的问题
    • 同步方法解决数据安全问题
    • Lock锁
    • 生产者和消费者模式----多线程协作问题

    2.2. Java中的线程基础内容讲解

     在Java中,“线程”指两件不同的事情:
     1、java.lang.Thread类的一个实例;
     2、线程的执行。
       在 Java程序中,有两种方法创建线程:
        	一是对 Thread 类进行派生并覆盖 run方法;
            二是通过实现Runnable接口创建。
            使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。
            一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。
            Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。
            一个Java应用总是从main()方法开始运行,main()方法运行在一个线程内,他被称为主线程。
            一旦创建一个新的线程,就产生一个新的调用栈。
            线程总体分两类:用户线程和守候线程。
            守护线程:守护线程的唯一用途就是为其他线程提供服务。
            当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的。
    

    2.3. Java中线程的两种创建方式

    2.3.1. Thread 类进行派生并覆盖 run方法

    package com.itlearn.threads;
    
    public class MyThread extends Thread{
        @Override
        public void run() {
            for (int i = 0;i<100;i++){
                System.out.println("MyThread->"+i);
            }
        }
    }
    

    ​ 可见:创建MyThread.java类,并继承Thread(java.lang.Thread),并重写run方法。即完成了MyThread线程的创建。

    下面介绍两种方法:

    方法名 说明
    void run() 在线程开启后,此方法将被调用执行
    void start() 使此线程开始执行,Java虚拟机会调用run方法()
    public class ThreadsDemo {
        public static void main(String[] args) {
            Thread thread1 = new MyThread();
            thread1.start();
        }
    }
    

    run()方法和start()方法的区别?

    ​ run():封装线程执行的代码,直接调用,相当于普通方法的调用

    ​ start():启动线程;然后由JVM调用此线程的run()方法

    也就是说,如果想启动一个线程,那么直接调用该线程的run方法是无效的,应该调用start方法,由虚拟机调用run方法。

    那么为什么要重写父类run方法?

    因为我们的MyThread类中可能还存在其他的代码,但不是所有的代码都需要被线程所执行,为了区分哪些代码是需要被线程执行的,Java提供了run方法来封装需要被线程执行的方法。

    2.3.2. 实现Runnable接口创建线程

    package com.itlearn.threads;
    
    public class MyThread_Runnable implements Runnable{
        @Override
        public void run() {
            for (int i = 0;i<100;i++){
                System.out.println("MyThread_Runnable->"+i);
            }
        }
    }
    

    ​ 可见:我们创建了MyThread_Runnable.java类,并实现了Runnable接口,所以必须实现该接口的方法,而该接口只有一个方法,即run()。我们在run()方法中可以部署我们的业务代码。

    ​ 那么我们如何启动该线程呢?

    ​ 首先介绍Thread类的两个构造方法

    方法名 说明
    Thread(Runnable target) 分配一个新的Thread对象
    Thread(Runnable target, String name) 分配一个新的Thread对象,并指定该线程名称

    ​ 直接上代码:

    public class ThreadsDemo {
        public static void main(String[] args) {
            //创建myThread_runnable对象
            MyThread_Runnable myThread_runnable = new MyThread_Runnable();
            //创建Thread类的对象,把MyRunnable对象作为构造方法的参数
            Thread thread2 = new Thread(myThread_runnable);
            thread2.start();
        }
    }
    

    ​ 可得出实现步骤如下:

    - 定义一个类MyThread_Runnable实现Runnable接口
    - 在MyThread_Runnable类中重写run()方法
    - 创建MyThread_Runnable类的对象
    - 创建Thread类的对象,把MyThread_Runnable对象作为构造方法的参数
    - 启动线程
    

    注:在实际应用中,我们推荐使用实现Runnable接口的方式创建线程。

    理由:

    • 避免了Java单继承的局限性
    • 适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想

    3. 线程优先级

    3.1. 优先级引入

    ​ 在Java中是支持多线程的,那么引发的问题就是:如果由多个线程,都要通过CPU的调度,那么调度的优先级是怎样的呢?即:这么多线程,谁优先执行谁后执行呢?在搞清楚这个问题之前我们必须明白线程的调度方式。

    3.2. 线程调度

    两种调度方式:

    • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
    • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

    注:Java使用的是抢占式调度模式

    调度随机性

    ​ 假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

    2.4.3. 线程优先级方法

    方法名 说明
    final int getPriority() 返回此线程的优先级
    final void setPriority(int newPriority) 更改此线程的优先级 线程默认优先级是5;线程优先级的范围是:1-10

    2.4.4. 抢占式和随机性验证

    1. 抢占式+随机性验证:

      package com.itlearn.threads;
      
      public class ThreadsDemo {
          public static void main(String[] args) {
              Thread thread1 = new MyThread();
              MyThread_Runnable myThread_runnable = new MyThread_Runnable();
              Thread thread2 = new Thread(myThread_runnable);
              
              thread1.start();
              thread2.start();
          }
      }
      

      即:将我们创建的两个线程同时启动。可见运行结果

    在这里插入图片描述

    即:两个线程并不是依次执行,而是在抢占CPU的资源,就是执行权,谁抢到了谁就执行。

    下面我们设置线程优先级,再次运行

    package com.itlearn.threads;
    
    public class ThreadsDemo {
        public static void main(String[] args) {
            Thread thread1 = new MyThread();
            MyThread_Runnable myThread_runnable = new MyThread_Runnable();
            Thread thread2 = new Thread(myThread_runnable);
    
            //获得线程当前优先级
            System.out.println("Thread1->"+thread1.getPriority());
            System.out.println("Thread2->"+thread1.getPriority());
            //设置线程优先级
            thread1.setPriority(4);
            thread2.setPriority(6);
    
            thread1.start();
            thread2.start();
        }
    }
    

    在这里插入图片描述

    结论:一个线程的优先级高,仅代表该线程获得CPU时间片的几率高,而并非是一定优先执行!

    4. 线程生存周期【重点理解】

    直接上图:

    在这里插入图片描述

    4.1. 线程的6种状态

    1. New(新建)
    2. Runnable(可运行)
    3. Blocked(阻塞)
    4. Waiting(等待)
    5. Timed Waiting(计时等待)
    6. Terminated(终止)

    4.1.1. 新建线程

    ​ 当用new操作符创建一个新线程时,如new Thread®,这个线程还没有开始运行。这意味着它的状态是新建(New),当一个线程处于新建状态时,程序还没有运行线程中的代码。

    4.1.2. 可运行线程

    ​ 一旦调用start方法,线程就处于可运行(Runnable)状态。一个可运行的线程也可能没有运行。要由操作系统为线程提供具体的运行时间。注意:Java规范没有将正在运行作为一个单独的状态。一个正在运行的线程仍然储于可运行状态。

    线程调度的细节依赖于操作系统提供的服务,抢占式调度系统给每一个可运行的线程一个时间片来执行任务。当时间片用完时,操作系统会剥夺该线程的运行权,并给另一个线程一个机会来运行,当选择下一个线程时,操作系统会考虑线程的优先级。

    ​ 记住:在任何给定时刻,一个可运行的线程可能正在运行也可能没有运行。(正式因为这样,这个状态被称为“可运行”而不是“运行”)。

    4.1.3. 阻塞和等待线程

    ​ 当线程处于阻塞或者等待状态时,它暂时是不活动的。它不运行任何的代码,而且消耗最少的资源。

    • 当一个线程视图获取一个内部对象锁的时候,而这个锁目前正在被其他线程占用,则该线程就会被阻塞。当有其他线程释放了这个锁,并且线程调度器允许该线程持有这个锁时,他将变成非阻塞状态。
    • 当线程等待另一个线程通知调度器出现一个条件时,这个线程就进入等待状态。调用Object.wait方法或者Thread.join方法,或者是等待java.util.concurrent库中的Lock或者Condition时,就会出现这种情况。实际上,阻塞状态和等待状态并没有多大区别。
    • 有几个方法有超时参数,调用这些方法就会让线程进入计时等待(Timed Waiting),这一状态将一直保持到超时期满或者接受到适当的通知。带有超时参数的方法有Thread.sleep和计时版的Object.wait、Thread.join、Lock.tryLock以及Condition.await。

    4.1.4. 终止线程

    线程会由于以下两个原因之一而终止:

    • run方法的正常退出,线程自然终止。
    • 因为一个没有捕获的异常终止了run方法,使线程异常终止。

    4.2. 线程状态转换

    在这里插入图片描述

    在这里插入图片描述

    4.2.1. 众人上厕所问题与线程状态对应理解。

    5. Java中多线程的线程同步----卖票案例

    5.1. 案例需求

    ​ 某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

    5.2. 实现步骤

    1. 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;

    2. 在SellTicket类中重写run()方法实现卖票,代码步骤如下

      1. 判断票数大于0,就卖票,并告知是哪个窗口卖的
      2. 卖了票之后,总票数要减1
      3. 票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行
      package com.itlearn.sell;
      
      public class SellTicket implements Runnable{
          private Integer tickets = 100;
          @Override
          public void run() {
              while (true){
                  if (tickets > 0){         	System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
                      tickets--;
                  }
              }
          }
      }
      
    3. 定义一个测试类SellTicketDemo,里面有main方法

      1. 创建SellTicket类的对象
      2. 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
      3. 启动线程
      package com.itlearn.sell;
      
      public class SellTicketDemo {
          public static void main(String[] args) {
              SellTicket sellTicket = new SellTicket();
      
              Thread thread1 = new Thread(sellTicket,"窗口1");
              Thread thread2 = new Thread(sellTicket,"窗口2");
              Thread thread3 = new Thread(sellTicket,"窗口3");
      
              thread1.start();
              thread2.start();
              thread3.start();
          }
      }
      

    5.3. 执行结果

    在这里插入图片描述

    5.4. 执行结果分析

    ​ 从上述执行结果看,似乎没有什么问题,但是在实际生活中,售票时是需要时间的,所以,在出售一张票的时候,需要一点时间的延迟。所以,我们可以借此来改进程序,设置每次出票时间为100毫秒,使用sleep方法来实现。

    package com.itlearn.sell;
    
    public class SellTicket implements Runnable{
        private Integer tickets = 100;
        @Override
        public void run() {
            while (true){
                if (tickets > 0){
                    try {
                        //使线程休眠100毫秒,来模拟出票延迟
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
                    tickets--;
                }
            }
        }
    }
    

    运行结果:

    在这里插入图片描述

    在这里插入图片描述

    6. 卖票案例中存在的问题分析

    6.1. 出现的问题

    1. 相同的票出现了多次,且有票丢失
    2. 出现了负数的票

    6.2. 出现该问题的原因

    6.2.1. 相同的票出现了多次,且有票丢失

    public class SellTicket implements Runnable {
        private int tickets = 100;
    
        @Override
        public void run() {
            //相同的票出现了多次
            while (true) {
                //tickets = 100;
                //t1,t2,t3
                //假设t1线程抢到CPU的执行权
                if (tickets > 0) {
                    //通过sleep()方法来模拟出票时间
                    try {
                        Thread.sleep(100);
                        //t1线程休息100毫秒
                        //t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里的时候,t2线程休息100毫秒
                        //t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //假设线程按照顺序醒过来
                   //t1抢到CPU的执行权,在控制台输出:窗口1正在出售第100张票
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    //t2抢到CPU的执行权,在控制台输出:窗口2正在出售第100张票
                    //t3抢到CPU的执行权,在控制台输出:窗口3正在出售第100张票
                    tickets--;
                    //如果这三个线程还是按照顺序来,这里就执行了3次--的操作,最终票就变成了97
                }
            }
    

    6.2.2. 出现了负数的票

     //出现了负数的票
            while (true) {
                tickets = 1;
                //t1,t2,t3
                //假设t1线程抢到CPU的执行权
                if (tickets > 0) {
                    //通过sleep()方法来模拟出票时间
                    try {
                        Thread.sleep(100);
                        //t1线程休息100毫秒
                        //t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里的时候,t2线程休息100毫秒
                        //t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候,t3线程休息100毫秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //假设线程按照顺序醒过来
                    //t1抢到了CPU的执行权,在控制台输出:窗口1正在出售第1张票
                    //假设t1继续拥有CPU的执行权,就会执行tickets--;操作,tickets = 0;
                    //t2抢到了CPU的执行权,在控制台输出:窗口1正在出售第0张票
                    //假设t2继续拥有CPU的执行权,就会执行tickets--;操作,tickets = -1;
                    //t3抢到了CPU的执行权,在控制台输出:窗口3正在出售第-1张票
                    //假设t3继续拥有CPU的执行权,就会执行tickets--;操作,tickets = -2;
                    System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                    tickets--;
                }
            }
        }
    }
    

    7. 同步代码块解决多线程中数据安全的问题

    7.1. 什么时候会出现数据安全的问题?

    1. 是否是多线程环境
    2. 是否有共享数据----如:出售的票
    3. 是否有多条语句操作共享数据-----如售票中多个线程操作同一个ticket对象

    7.2. 如何解决多线程的安全问题呢?----(同步代码块方案)

    • 基本思想:让程序没有出现安全问题的环境,即:破坏7.1中的三个条件

      注意:7.1中,前两个条件是我们无法破坏的,我们只有在第三个条件之上进行操作以解决多线程数据安全问题。

    • 实现方法:

    • 把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可------Java提供了同步代码块的方式来解决

    • 同步代码块的格式

      synchronized(任意对象) { 
      	多条语句操作共享数据的代码 
      }
      

      synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

      public void run() {
              while (true){
                   synchronized (new Object()){
                  	if (tickets > 0){
                          try {
                              //使线程休眠100毫秒,来模拟出票延迟
                              Thread.sleep(100);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
                          tickets--;
                      }
                  }
              }
      

      ​ 可见:在执行售票程序中,加入了 synchronized(new Object()),即给该段程序加入了锁。因为synchronized(任意对象)参数使任意对象,则我们先随意new一个Obejct类的对象。下面来观察运行结果。

    在这里插入图片描述

    可以看到,这里我们明明加入了锁,为什么还是会出现同一张票出售多次的问题呢?

    问题分析:

    ​ 这里是因为,我们在程序段中虽然加入了锁(new Object()),但是三个线程都可以运行到这里,并且每个线程都可以创建自己的锁,即,这里是每个线程都有自己的锁,这就导致了多把锁的现象,而我们需要的是只有一把锁,如上厕所现象中的锁一般,每个线程进入到这段代码的时候,会请求锁,如果有了锁才可以运行。

    ​ 所以,我们只需要一把锁,即把new Object()的操作放到while外面,而在synchronized(任意对象)中,只传入我们new的对象引用即可。如下:

    package com.itlearn.sell;
    
    public class SellTicket implements Runnable{
        private Integer tickets = 100;
        private Object obj = new Object();
        @Override
        public void run() {
            while (true){
                synchronized (obj){
                if (tickets > 0){
                        try {
                            //使线程休眠100毫秒,来模拟出票延迟
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
                        tickets--;
                    }
                }
            }
        }
    }
    

    执行结果:

    在这里插入图片描述

    问题解决!

    8. 同步方法解决数据安全问题

    8.1. 同步方法介绍

    ​ 同步方法:即将synchronized关键字加到方法上。使其对方法体进行加锁。

    ​ 那么,在方法上加锁,就可以解决数据安全问题吗?所加的锁对象是谁的呢?请看下面程序。

    package com.itlearn.sell;
    
    public class SellTicket implements Runnable{
        private Integer tickets = 100;
        private Object obj = new Object();
        private Integer x = 0;
        @Override
        public void run() {
            while (true){
                if (x % 2 ==0){
                    synchronized (obj){
                        if (tickets > 0){
                            try {
                                //使线程休眠100毫秒,来模拟出票延迟
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
                            tickets--;
                        }
                    }
                }else {
                    setTickets();
                }
                x++;
            }
        }
        
        public synchronized void setTickets(){
            if (tickets > 0){
                try {
                    //使线程休眠100毫秒,来模拟出票延迟
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
                tickets--;
            }
        }
    }
    

    执行结果:

    在这里插入图片描述

    ​ 可见:出现了数据安全问题。那么为什么会这样呢?

    ​ 问题分析:

    在上述代码中,虽然在方法上加上了synchronized,即,对该方法上了锁,但是,我们知道,一个类中的方法,只要不是静态方法,那么这个方法的实际操作对象是this,即本类的对象。而在run中,我们是对obj加入的锁,即两个锁并不是同一个锁,所以会出现该线程问题。

    ​ 问题解决:【重点】

    ​ 我们可以在run中,锁对象设置为this,即本类对象,那么就和方法中锁的对象保持一致了。如下代码:

    public void run() {
            while (true){
                if (x % 2 ==0){
                    synchronized (this){
                        if (tickets > 0){
                            try {
                                //使线程休眠100毫秒,来模拟出票延迟
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
                            tickets--;
                        }
                    }
                }else {
                    setTickets();
                }
                x++;
            }
        }
    

    运行结果:

    在这里插入图片描述

    可见,线程问题解决。

    8.2. 静态同步方法

    同步静态方法:就是把synchronized关键字加到静态方法上。注意,静态方法属于类本身

    修饰符 static synchronized 返回值类型 方法名(方法参数) { 
    	方法体;
    }
    

    同步静态方法的锁对象是什么呢?

    ​ 即:类的字节码文件:类名.class

    package com.itlearn.sell;
    
    public class SellTicket implements Runnable{
        private static Integer tickets = 100;
        private Integer x = 0;
        @Override
        public void run() {
            while (true){
                if (x % 2 ==0){
                    synchronized (SellTicket.class){
                        if (tickets > 0){
                            try {
                                //使线程休眠100毫秒,来模拟出票延迟
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
                            tickets--;
                        }
                    }
                }else {
                    setTickets();
                }
                x++;
            }
        }
        public static synchronized void sellTickets(){
            if (tickets > 0){
                try {
                    //使线程休眠100毫秒,来模拟出票延迟
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"张票");
                tickets--;
            }
        }
    }
    

    注意:静态方法只能操作静态成员变量,所以需要把tickets设为static。

    9. Lock锁

    ​ 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。

    ​ Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化。

    • ReentrantLock构造方法

      方法名 说明
      ReentrantLock() 创建一个ReentrantLock的实例
    • 加锁解锁方法

      方法名 说明
      void lock() 获得锁
      void unlock() 释放锁
    • 代码演示

      public class SellTicket implements Runnable {
          private int tickets = 100;
          private Lock lock = new ReentrantLock();
      
          @Override
          public void run() {
              while (true) {
                  try {
                      lock.lock();
                      if (tickets > 0) {
                          try {
                              Thread.sleep(100);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                          System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
                          tickets--;
                      }
                  } finally {
                      lock.unlock();
                  }
              }
          }
      }
      public class SellTicketDemo {
          public static void main(String[] args) {
              SellTicket st = new SellTicket();
      
              Thread t1 = new Thread(st, "窗口1");
              Thread t2 = new Thread(st, "窗口2");
              Thread t3 = new Thread(st, "窗口3");
      
              t1.start();
              t2.start();
              t3.start();
          }
      }
      

    10. 生产者和消费者模式----多线程协作问题

    10.1.生产者和消费者模式概述

    1. 概述

      ​ 生产者消费者模式是一个十分经典的多线程协作的模式,弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。

    2. 什么是生产者消费者问题?

      所谓生产者消费者问题,实际上主要是包含了两类线程:

      1. 一类是生产者线程用于生产数据
      2. 一类是消费者线程用于消费数据
    3. 为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

      生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

      消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

      在这里插入图片描述

    4. 下面介绍必要的Object类的等待和唤醒的方法

      方法名 说明
      void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
      void notify() 唤醒正在等待对象监视器的单个线程
      void notifyAll() 唤醒正在等待对象监视器的所有线程

    10.2. 生产者和消费者案例

    1. 案例需求

      生产者消费者案例中包含的类:

      奶箱类(Box):定义一个成员变量,表示第x瓶奶,提供存储牛奶和获取牛奶的操作

      生产者类(Producer):实现Runnable接口,重写run()方法,调用存储牛奶的操作

      消费者类(Customer):实现Runnable接口,重写run()方法,调用获取牛奶的操作

    2. 测试类(BoxDemo):里面有main方法,main方法中的代码步骤如下

      ①创建奶箱对象,这是共享数据区域

      ②创建消费者创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作

      ③对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作

      ④创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递

      ⑤启动线程

    3. 代码实现

      public class Box {
          //定义一个成员变量,表示第x瓶奶
          private int milk;
          //定义一个成员变量,表示奶箱的状态
          private boolean state = false;
      
          //提供存储牛奶和获取牛奶的操作
          public synchronized void put(int milk) {
              //如果有牛奶,等待消费
              if(state) {
                  try {
                      wait();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
      
              //如果没有牛奶,就生产牛奶
              this.milk = milk;
              System.out.println("送奶工将第" + this.milk + "瓶奶放入奶箱");
      
              //生产完毕之后,修改奶箱状态
              state = true;
      
              //唤醒其他等待的线程
              notifyAll();
          }
      
          public synchronized void get() {
              //如果没有牛奶,等待生产
              if(!state) {
                  try {
                      wait();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
      
              //如果有牛奶,就消费牛奶
              System.out.println("用户拿到第" + this.milk + "瓶奶");
      
              //消费完毕之后,修改奶箱状态
              state = false;
      
              //唤醒其他等待的线程
              notifyAll();
          }
      }
      
      public class Producer implements Runnable {
          private Box b;
      
          public Producer(Box b) {
              this.b = b;
          }
      
          @Override
          public void run() {
              for(int i=1; i<=30; i++) {
                  b.put(i);
              }
          }
      }
      
      public class Customer implements Runnable {
          private Box b;
      
          public Customer(Box b) {
              this.b = b;
          }
      
          @Override
          public void run() {
              while (true) {
                  b.get();
              }
          }
      }
      
      public class BoxDemo {
          public static void main(String[] args) {
              //创建奶箱对象,这是共享数据区域
              Box b = new Box();
      
              //创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
              Producer p = new Producer(b);
              //创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
              Customer c = new Customer(b);
      
              //创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
              Thread t1 = new Thread(p);
              Thread t2 = new Thread(c);
      
              //启动线程
              t1.start();
              t2.start();
          }
      }
      

    (int i=1; i<=30; i++) {
    b.put(i);
    }
    }
    }

    public class Customer implements Runnable {
    private Box b;

       public Customer(Box b) {
           this.b = b;
       }
    
       @Override
       public void run() {
           while (true) {
               b.get();
           }
       }
    

    }

    public class BoxDemo {
    public static void main(String[] args) {
    //创建奶箱对象,这是共享数据区域
    Box b = new Box();

           //创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
           Producer p = new Producer(b);
           //创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
           Customer c = new Customer(b);
    
           //创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
           Thread t1 = new Thread(p);
           Thread t2 = new Thread(c);
    
           //启动线程
           t1.start();
           t2.start();
       }
    

    }

    
    
    
    展开全文
  • windows创建线程

    2019-07-22 15:35:15
    程序(.EXE文件):磁盘上可执行文件,属性是文件 进程:可执行文件在内存中的映射,进程本身无法... 线程:操作系统分配cpu时间最小单位。 进程启动时,操作系统会自动创建这个进程第一个线程(主线程),...
  • Windows 线程笔记

    2015-04-16 15:23:40
    1 线程是程序执行流的最小单元,因此也是独立调度和分配CPU的最小单位,也被称为轻量级进程。每个程序至少有一个线程,若一个程序只有一个线程,则称为单线程程 序, 在一个程序同时运行多个线程执行不同的任务...
  • windows编程之线程操作

    千次阅读 2016-07-04 23:39:13
    我们知道,进程是资源分配的最小单位,而线程呢,则是CPU运行最小单位。 在最早DOS系统下,CPU每次只能运行一个进程且该进程只有一个线程,当该进程执行完退出后,CPU 才能加载另外一个进程。而现在win系统...
  • Windows多线程编程

    2010-11-01 11:24:00
    而线程是进程一个实体,是CPU调度和分派基本单位,它是比进程更小能独立运行基本单位。 线程自己基本上不拥有系统资源,只拥有一点在运行必不可少资源(如程序计数器,一组寄存器和栈),但是它可与...
  • 面试遇到问题

    千次阅读 2013-02-26 19:53:43
    1.线程和进程联系和区别 进程是表示资源分配的基本单位,又是调度运行基本单位。例如,用户运行自己程序,系统就创建一...所以,进程是系统中的并发执行的单位。 在Mac、Windows NT等采用微内核结构
  • python中的多进程

    2018-12-11 13:32:19
    进程 ...进程——资源分配的基本单位  线程——是CPU调度最小单位. fork子进程(linux上,不能跨平台在windows)     res = os.fork() 它有两个返回值,是两次分别返回,也就是一次返...
  • 我是一直都以为这个问题答案是肯定,也就是说可以运行在多核上。 但是有一天见到这样一个理论,我就顿时毁三观了。 JVM在操作系统中是作为一个进程,java所有线程都运行自这个... 在windows中进程是不活动
  • Java中的线程之Thread类

    2019-12-05 15:15:51
    通过使用线程,可以把操作系统进程资源分配和执行调度分开,各个线程既可以共享进程资源(内存地址、文件I/O等),又可以独立调度(线程是CPU调度基本单位)。 主流操作系统(Windows,Linux)都提供了线程...
  • 进程是系统进行资源分配和调度独立单位,一个进程下可有多个线程.最直观就是打开windows的任务管理器,可以看到目前电脑中的每个进程.一个exe就是一个进程.每个进程在运行时,总会有各种子任务去执行,这就需要多...
  • 输入htop命令,可查看各核使用情况...线程:线程是操作系统调度的单位,一个进程可以有多个线程。线程切换需要资源一般,效率也一般。 进程:一个程序运行起来后,代码+用到资源称之为进程,它是操作系统分配...
  • Windows程序设计.rar

    2008-10-31 18:32:12
    以下是侯捷个人陆续收集整理有关於我所涉猎领域术语对照(英繁简)。 欢迎所有朋友给我意见(任何意见)。谢谢。 新书写作,或发表文章时,我会以此表为叁考。 本表所列,并不表示我在写译书籍时一定会...
  • 进程与线程区别

    2018-03-01 16:52:33
    在Mac、Windows NT等采用微内核结构操作系统,进程功能发生了变化:它只是资源分配的单位,而不再是调度运行的单位。在微内核系统,真正调度运行基本单位是线程。因此,实现并发功能的单位是线程。b.进程...
  • 进程概念 ...在Mac、Windows NT等采用微内核结构操作系统,进程功能发生了变化:它只是资源分配的单位,而不再是调度运行的单位。在微内核系统,真正调度运行基本单位是线程。因此,实现并发
  • 线程与进程区别

    2016-06-17 11:59:41
    在Mac、Windows NT等采用微内核结构操作系统,进程功能发生了变化:它只是资源分配的单位,而不再是调度运行的单位。在微内核系统,真正调度运行基本单位是线程。因此,实现并发功能的单位是线程。 ...
  • 1. 在进程可以有多个线程同时执行代码。进程之间是相对独立,一个进程无法访问另一个进程... 线程(Thread)是进程中的基本执行单元,是操作系统分配CPU时间基本单位,一个进程可以包含若干个线程,在进程入口...
  • JS单线程运行机制

    2020-06-07 16:57:28
    一. 区分进程和线程 很多新手是区分不清线程和进程,没有关系。这很正常。先看看下面这个形象比喻: ...进程是 cpu 资源分配的最小单位(是能拥有资源和独立运行最小单位) 线程是 cpu 调度最小单位
  • 进程和线程详解

    2018-09-21 20:10:00
    进程概念  进程是表示资源分配的基本单位,又是调度运行基本单位。例如,用户运行自己程序,系统就创建一个进程,并为它分配资源,包括各种...在Mac、Windows NT等采用微内核结构操作系统,进程功能发...
  • 进程概念  进程是表示资源分配的基本单位,又是调度运行基本单位。例如,用户运行自己程序,系统就创建一个进程,并为它分配资源,包括...在Mac、Windows NT等采用微内核结构操作系统,进程功能发生了...
  • 多线程: 进程是操作系统资源分配的基本单位,而线程是任务调度和...系统可以分配给每个进程一段有限使用CPU时间,CPU在这个时间片中执行某个进程,然后下一个时间片又跳到另一个进程去执行,由于CPU转换较快...
  • 进程、线程优缺点

    千次阅读 2017-11-28 18:46:20
    进程概念   进程是表示资源分配的基本单位,又是调度运行基本单位。例如,用户运行自己程序,系统就创建一个进程,并为它分配资源,包括各种表格... 在Mac、Windows NT等采用微内核结构操作系统,进程
  • Linux下进程管理

    2017-11-15 17:06:00
    windows中分为进程和线程,而这些概念在Linux中同样适用,而所谓的进程process既是资源分配单位,一个进程的发起想要调度CPU进行执行则需要或得CPU的时间片,同样在获得CUP的时间片后,还有相应的优先级,从而...

空空如也

空空如也

1 2 3 4 5 ... 14
收藏数 266
精华内容 106
关键字:

windows中cpu的分配单位