线程

来源:互联网 发布:java socket保持心跳 编辑:程序博客网 时间:2024/05/18 03:31

线程

一、简介

1.定义

线程,有时被称为轻量级进程(Lightweight ProcessLWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。

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

线程也有就绪、阻塞和运行三种基本状态。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程

2.特点

在多线程OS中,通常是在一个进程中包括多个线程,每个线程都是作为利用CPU的基本单位,是花费最小开销的实体。线程具有以下属性。

1)轻型实体

线程中的实体基本上不拥有系统资源,只是有一点必不可少的、能保证独立运行的资源,比如,在每个线程中都应具有一个用于控制线程运行的线程控制块TCB,用于指示被执行指令序列的程序计数器、保留局部变量、少数状态参数和返回地址等的一组寄存器和堆栈。

2)独立调度和分派的基本单位

在多线程OS中,线程是能独立运行的基本单位,因而也是独立调度和分派的基本单位。由于线程很,故线程的切换非常迅速且开销小(在同一进程中的)。

3)可并发执行

在一个进程中的多个线程之间,可以并发执行,甚至允许在一个进程中所有线程都能并发执行;同样,不同进程中的线程也能并发执行,充分利用和发挥了处理机与外围设备并行工作的能力。

4)共享进程资源

在同一进程中的各个线程,都可以共享该进程所拥有的资源,这首先表现在:所有线程都具有相同的地址空间(进程的地址空间),这意味着,线程可以访问该地址空间的每一个虚地址;此外,还可以访问进程所拥有的已打开文件、定时器、信号量机构等。由于同一个进程内的线程共享内存和文件,所以线程之间互相通信不必调用内核。

3.线程与进程的比较

进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。另外,进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。当进程发生调度时,不同的进程拥有不同的虚拟地址空间,而同一进程内的不同线程共享同一地址空间。与进程相对应,线程与资源分配无关,它属于某一个进程,并与进程内的其他线程一起共享进程的资源。

线程只由相关堆栈(系统栈或用户栈)寄存器和线程控制表TCB组成。寄存器可被用来存储线程内的局部变量,但不能存储其他线程的相关变量。

通常在一个进程中可以包含若干个线程,它们可以利用进程所拥有的资源。在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位。由于线程比进程更小,基本上不拥有系统资源,故对它的调度所付出的开销就会小得多,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。因而近年来推出的通用操作系统都引入了线程,以便进一步提高系统的并发性,并把它视为现代操作系统的一个重要指标。

线程与进程的区别可以归纳为以下4点:

1)地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。

2)通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。

3)调度和切换:线程上下文切换比进程上下文切换要快得多。

4)在多线程OS中,进程不是一个可执行的实体。

 

二、线程创建函数

微软在Windows API中提供了建立新的线程的函数CreateThread,当使用CreateProcess调用时,系统将创建一个进程和一个主线程

CreateThread将在主线程的基础上创建一个新线程,大致做如下步骤: 
1在内核对象中分配一个线程标识/句柄,可供管理,由CreateThread返回 
2把线程退出码置为STILL_ACTIVE,把线程挂起计数置
3分配context结构 
4分配两页的物理存储以准备栈,保护页设置为PAGE_READWRITE,第2页设为PAGE_GUARD 
5 lpStartAddrlpvThread值被放在栈顶,使它们成为传送给StartOfThread的参数 
6context结构的栈指针指向栈顶(第5步)指令指针指向startOfThread函数 
MSDNCreateThread原型: 

HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes,   DWORD dwStackSize,   LPTHREAD_START_ROUTINE lpStartAddress,   LPVOID lpParameter,   DWORD dwCreationFlags,   LPDWORD lpThreadId); 
参数说明: 
lpThreadAttributes:指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,它被设为NULL,表示使用缺省值。 
dwStackSize:线程堆栈大小,一般=0,在任何情况下,Windows根据需要动态延长堆栈的大小。 
lpStartAddress:指向线程函数的指针,形式:@函数名,函数名称没有限制,但是必须以下列形式声明: 
  DWORD WINAPI ThreadProc (LPVOID pParam) ,格式不正确将无法调用成功。 
lpParameter:向线程函数传递的参数,是一个指向结构的指针,不需传递参数时,为NULL。 
dwCreationFlags :线程标志,可取值如下 

CREATE_SUSPENDED: 创建一个挂起的线程 
  :创建后立即激活。 
lpThreadId:保存新线程的id。 

返回值:

函数成功,返回线程句柄;函数失败返回false。 
函数说明:

创建一个线程。语法: 
hThread = CreateThread (&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ; 
  一般并不推荐使用 CreateTheard函数,而推荐使用RTL 库里的System单元中定义的 BeginTheard函数,因为这除了能创建一个线程和一个入口函数以外,还增加了几项保护措施。 

三、多线程示例

#include "stdafx.h"#include<windows.h>//需要访问Windows API函数CreateThread#include <iostream>using namespace std;DWORD WINAPI Fun1Proc(//注意线程函数的声明形式LPVOID pParam //thread data);DWORD WINAPI Fun2Proc(LPVOID pParam);int index=0;int _tmain(int argc, _TCHAR* argv[]){HANDLE hThread1;HANDLE hThread2;hThread1 = CreateThread(NULL,//让新线程使用默认的安全性0,//让新线程采用与调用线程一样的栈大小Fun1Proc,//指定线程1入口函数的地址NULL,//传递给线程1的参数 可以是结构体(包含多个数据),此处不需要使用这个参数 设为NULL0,//线程创建标记 设为0 让线程一旦创建就立即运行NULL);//新线程的ID 这里不需要使用该ID 设为NULLCloseHandle(hThread1);//关闭新线程的句柄hThread2 = CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);CloseHandle(hThread2);//关闭新线程的句柄//while (++index < 1000)cout<<"main thread is running\r\n"<<endl;//Sleep(10);//延时10mssystem("pause");return 0;}//线程1的入口函数DWORD WINAPI Fun1Proc(LPVOID pParam){//while (++index < 1000)cout<<"thread1 is running\r\n"<<endl;return 0;} //线程1的入口函数DWORD WINAPI Fun2Proc(LPVOID pParam){//while (++index < 1000)cout<<"thread2 is running\r\n"<<endl;return 0;}

运行结果:

 

四、线程同步

4.1定义

同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。“同”字从字面上容易理解为一起动作。其实不是,“同”字应是指协同、协助、互相配合。如进程、线程同步,可理解为进程或线程AB一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给AA再继续操作。

所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。例如Window API函数SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的LRESULT值返回给调用者。

在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。

4.2线程同步的方式和机制

临界区、互斥区、事件、信号量四种方式

临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的区别

1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占;

2、互斥量:采用互斥对象机制。 只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享;

3、信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目;

4、事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作。 

五、利用互斥对象实现线程同步

5.1互斥对象

互斥对象(mutex)属于内核对象,它能确保线程拥有对单个资源的互斥访问权。互斥对象包含一个使用数量,一个线程ID和一个计数器。其中ID用于标识系统中哪个线程当前拥有互斥对象,计数器用于指明该线程拥有互斥对象的次数。

为了创建互斥对象,需要调用函数:CreateMutex,该函数可以创建或打开一个命名的或匿名的互斥对象,然后程序就可以利用互斥对象完成线程间的同步。CreateMutex函数原型声明如下:

HANDLE CreateMutex(LPSECURITY_ATTRIBUTESlpMutexAttributes, // 指向安全属性的指针BOOLbInitialOwner, // 初始化互斥对象的所有者LPCTSTRlpName // 指向互斥对象名的指针);

参数 类型及说明

lpMutexAttributes SECURITY_ATTRIBUTES,指定一个SECURITY_ATTRIBUTES结构,或传递零值(将参数声明为ByVal As Long,并传递零值),表示使用不允许继承的默认描述符

bInitialOwner Long,如创建进程希望立即拥有互斥体,则设为TRUE。一个互斥体同时只能由一个线程拥有

lpName String,指定互斥体对象的名字。用vbNullString创建一个未命名的互斥体对象。如已经存在拥有这个名字的一个事件,则打开现有的已命名互斥体。这个名字可能不与现有的事件、信号机、可等待计时器或文件映射相符

返回值

Long,如执行成功,就返回互斥体对象的句柄;零表示出错。会设置GetLastError。即使返回的是一个有效句柄,但倘若指定的名字已经存在,GetLastError也会设为ERROR_ALREADY_EXISTS 

5.2示例

#include "stdafx.h"#include<windows.h>//需要访问Windows API函数CreateThread#include <iostream>using namespace std;DWORD WINAPI Fun1Proc(//注意线程函数的声明形式LPVOID pParam //thread data);DWORD WINAPI Fun2Proc(LPVOID pParam);int index=0;int tickets=100;HANDLE hMutex;int _tmain(int argc, _TCHAR* argv[]){HANDLE hThread1;HANDLE hThread2;hThread1 = CreateThread(NULL,//让新线程使用默认的安全性0,//让新线程采用与调用线程一样的栈大小Fun1Proc,//指定线程1入口函数的地址NULL,//传递给线程1的参数 可以是结构体(包含多个数据),此处不需要使用这个参数 设为NULL0,//线程创建标记 设为0 让线程一旦创建就立即运行NULL);//新线程的ID 这里不需要使用该ID 设为NULLhThread2 = CreateThread(NULL,0,Fun2Proc,NULL,0,NULL);CloseHandle(hThread1);//关闭新线程的句柄CloseHandle(hThread2);//关闭新线程的句柄//创建互斥对象hMutex=CreateMutex(NULL,//让互斥对象拥有默认的安全性FALSE,//该线程将不会获得所创建的互斥对象的所有权 true 相反NULL);//创建一个匿名的互斥对象Sleep(4000);//延时4ssystem("pause");return 0;} //线程1的入口函数DWORD WINAPI Fun1Proc(LPVOID pParam){while (true){WaitForSingleObject(hMutex,INFINITE);if (tickets>0){Sleep(1);cout<<"thread1 sell tickets:\r\n"<<tickets--<<endl;} else{break;}ReleaseMutex(hMutex);//释放互斥对象}return 0;} //线程2的入口函数DWORD WINAPI Fun2Proc(LPVOID pParam){while (true){//实现线程必须主动请求共享对象的使用权才有可能获得该所有权WaitForSingleObject(hMutex,//所请求对象的句柄INFINITE);//指定等待的时间间隔,以毫秒为单位if (tickets>0){Sleep(1);cout<<"thread2 sell tickets:\r\n"<<tickets--<<endl;} else{break;}ReleaseMutex(hMutex);}return 0;} 

运行结果:

 

 

 

0 0
原创粉丝点击