多线程笔记5

第六章:Overlapped I/O,在你身后变戏法

1.overlapped I/O 是 Win32 的一项技术,你可以要求操作系统为你传送数据,并且在传送完毕时通知你。这项技术使你的程序在I/O 进行过程中仍然能够继续处理事务。事实上,操作系统内部正是以线程来完成 overlapped I/O。

2.Win32文件操作函数

(1)CreateFile()可以用来打开文件、串行口和并行口、Named pipes、Console。

HANDLE CreateFile(
LPCTSTR lpFileName,                                         // 指向文件名称
DWORD dwDesiredAccess,                                  // 存取模式(读或写)
DWORD dwShareMode,                                      // 共享模式(share mode)
LPSECURITY_ATTRIBUTES lpSecurityAttributes,  // 指向安全属性结构
DWORD dwCreationDisposition,                          // 如何产生
DWORD dwFlagsAndAttributes,                          // 文件属性
HANDLE hTemplateFile                                      // 一个临时文件,将拥有全部的属性拷贝
);

注:overlapped I/O性质:可以在同一时间读写文件的许多部分;多个overlapped请求,执行次序无法保证;overlapped I/O的基本型式是以ReadFile()和WriteFile()完成的。

(2)ReadFile()

BOOL ReadFile(
HANDLE hFile,                                     // 欲读之文件
LPVOID lpBuffer,                                 // 接收数据之缓冲区
DWORD nNumberOfBytesToRead,         // 欲读取的字节个数
LPDWORD lpNumberOfBytesRead,        // 实际读取的字节个数的地址
LPOVERLAPPED lpOverlapped               // 指针,指向 overlapped info
);

(3)WriteFile()

BOOL WriteFile(
HANDLE hFile,                                   // 欲写之文件
LPCVOID lpBuffer,                             // 储存数据之缓冲区
DWORD nNumberOfBytesToWrite,       // 欲写入的字节个数
LPDWORD lpNumberOfBytesWritten,   // 实际写入的字节个数的地址
LPOVERLAPPED lpOverlapped             // 指针,指向 overlapped info
);

如果CreateFile() 的第6个参数被指定为FILE_FLAG_ OVERLAPPED,就必须在上述的 lpOverlapped 参数中提供一个指针,指向一个 OVERLAPPED 结构。

(4)OVERLAPPED结构

typedef struct _OVERLAPPED {
DWORD Internal;        //通常保留。当GetOverlappedResult传回False,并且GetLastError并非传回ERROR_IO_PENDING,则内含一个视系统而定的状态。
DWORD InternalHigh; //通常被保留,当GetOverlappedResult传回True,则内含“被传输数据的长度”
DWORD Offset;          //读、写偏移位置,从文件头开始算起。若目标设备不支持文件位置,忽略
DWORD OffsetHigh;   //64位文件偏移中较高32位。若目标设备不支持文件位置,忽略
HANDLE hEvent;       //manual reset event,overlapped I/O完成时被激发。ReadFileEX,WriteFileEX忽略这个栏位,彼时被用来传递一个用户自定义的指针。
} OVERLAPPED, *LPOVERLAPPED;

OVERLAPPED 结构执行两个重要的功能。第一,它像一把钥匙,用以识别每一个目前正在进行的 overlapped 操作。第二,它在你和系统之间提供了一个共享区域,参数可以在该区域中双向传递。

通常overlapped结构存放在heap中。

3.被激发的File Handles

(1)异步IO的步骤:CreateFile指定FILE_FLAG_OVERLAPPED;设立一个OVERLAPPED结构,调用ReadFile、WriteFile带上这个参数。

(2)文件handle是一个核心对象,一旦操作完毕即被激发。

(3)GetOverlappedResult()

BOOL GetOverlappedResult(
HANDLE hFile,                                        //文件设备的handle
LPOVERLAPPED lpOverlapped,                 //一个指针,指向overlapped结构
LPDWORD lpNumberOfBytesTransferred,  //一个指针,指向DWORD,保存真正被传输的字节数。
BOOL bWait                                          //是否要等待操作完成,TRUE表示等待。
);

(4)虽然你要求一个overlapped 操作,但它并不一定就是 overlapped!如果数据已经被放进 cache中,或如果操作系统认为它可以很快速地取得那份数据,那么文件操作就会在ReadFile() 返回之前完成,而 ReadFile() 将传回 TRUE。

(5)一个文件操作为 overlapped,而操作系统把“操作请求”放到队列中等待执行, ReadFile() 和、WriteFile()都会传回 FALSE 以示失败。这个行为并不是很直观, 你必须调用GetLastError() 并确定它传回 ERROR_IO_PENDING,那意味着“overlappedI/O 请求”被放进队列之中等待执行。GetLastError() 也可能传回其他的值,例如 ERROR_HANDLE_EOF,那就真正代表一个错误了。

4.被激发的event对象

(1)所使用的 event 对象必须是手动重置(manual-reset)而非自动重置(auto-reset)。

(2)IOBYEVENT例子。

5.异步过程调用(Asynchronous Procedure Calls,APCs)

(1)使用overlapped I/O与event搭配的两个问题:

<1>WaitForMultipleObjects最多等待64个对象。

<2>必须不断的根据“哪一个handle被激发”而计算如何反应。

(2)使用Ex版的ReadFile和WriteFile,可以使用异步过程调用机制。只有当线程处于alertable状态时,APCs才会被调用。当线程因为以下5个函数而处于等待状态,且线程的“alertable”标记被设为TRUE,则线程处于alertable状态:

SleepEx()

WaitForSingleObjectEx()

WaitForMultipleObjectEx()

MsgWaitForMultipleObjectsEx()

SignalObjectAndWait();

(3)用于 overlapped I/O 的 APCs 是一种所谓的 user mode APCs。WindowsNT 另有一种所谓的 kernel mode APCs。Kernel mode APCs 也会像 usermode APCs 一样被保存起来,但一个 kernel mode APC 一定会在下一个timeslice 被调用,不管线程当时正在做什么。 Kernel mode APCs 用来处理系统机能,不在应用程序的控制之中。

(4)提供的 I/O completion routine 应该有这样的型式:

VOID WINAPI FileIOCompletionRoutine(
DWORD dwErrorCode,   //0表示操作完成,ERROR_HANDLE_EOF表示操作已经到了文件尾端。
DWORD dwNumberOfBytesTransferred,//真正被传输的数据字节数
LPOVERLAPPED lpOverlapped//指向overlapped结构,此结构由开启overlapped I/O操作的函数提供
);

(5)使用 APCs 时,OVERLAPPED 结构中的 hEvent 栏位不需要用来放置一个 event handle。Win32 文件上说此时 hEvent 栏位可以由程序员自由运用。那么最大的用途就是:首先配置一个结构,描述数据来自哪里,或是要对数据进行一些什么操作,然后将 hEvent 栏位设定指向该结构

(6)在C++ 中产生一个I/O Completion Routines:储存一个指针,指向用户自定义数据(一个对象),然后经由此指针调用一个 C++ 成员函数。由于 static 成员函数是类的一部分,你还是可以调用 private 成员函数。

6.对文件进行overlapped I/O的缺点

(1)似乎 Windows NT 是以“I/O 请求”的大小来决定要不要将此请求先记录下来。所以对于数据量小的操作,overlapped I/O的效率反而更低。

(2)解决办法:以少量的线程负责所有的硬盘 I/O,然后把这些线程的I/O 请求,保持在一个队列之中。这种效率比较高。

(3)有两种情况,overlapped I/O 总是同步执行,甚至即使 FILE_FLAG_NO_BUFFERING 已经指定。第一种情况是你进行一个写入操作而造成文件的扩展。第二种情况是你读写一个压缩文件。

7.I/O Completion Ports

(1)APCs的缺点:最大的问题就是,有好几个 I/O APIs 并不支持 APCs,如listen() 和 WaitCommEvent() 便是两个例子。APCs 的另一个问题是,只有发出“overlapped 请求”的那个线程才能够提供 callback 函数,然而在一个“scalable”(译注)系统中,最好任何线程都能够服务 events。

(2)产生一个I/O Completion Port

HANDLE CreateIoCompletionPort(
HANDLE FileHandle,                        //文件或设备的handle,若为INVALID_HANDLE_VALUE,则产生一个没有和任何handle关联的port。
HANDLE ExistingCompletionPort,      //若此栏位被指定,则FileHandle被加到此port上。指定Null产生一个新的port。
DWORD CompletionKey,                  //用户自定义的一个数值,将被交给提供服务的线程。此值和FileHanlde有关联。
DWORD NumberOfConcurrentThreads//与此I/O completion port 有关联的线程个数。
);

(3)与一个文件handle产生关联

  再次使用CreateIoCompletionPort接口。

(4)在一个I/O Completion Port上等待

BOOL GetQueuedCompletionStatus(
HANDLE CompletionPort,                        //将在其上等待completion port。
LPDWORD lpNumberOfBytesTransferred,  //指向DWORD,收到“被传输的数据字节数”。
LPDWORD lpCompletionKey,                   //指向DWORD,该DWORD将收到由CreateIoCompletionPort定义的key。
LPOVERLAPPED *lpOverlapped,               //overlapped结构指针的地址。
DWORD dwMilliseconds                          //等待的最长时间,时间终了,lpOverlapped被设为NULL,函数传回FALSE。
);

在completion port上等待的线程是以先进后出的次序提供服务。

(5)避免Completion Packets

设定一个 OVERLAPPED 结构,内含一个合法的手动重置(manual-reset)event 对象,放在 hEvent 栏位。然后把该 handle 的最低位设为 1。

overlap.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

overlap.hEvent = (HANDLE)((DWORD)overlap.hEvent | 0x1);

WriteFile(hFile, buffer, 128,& dwBytesWritten, &overlap);

8.对Sockets使用Overlapped I/O

分析ECHO例子,多实践socket IOCP。

时间: 2024-10-13 11:36:36

多线程笔记5的相关文章

多线程笔记

1.多线程的创建方式有两种 a 实现Runnable的接口 实现他的run的方法 建议使用这种 因为接口可以实现多继承 b 集成Thread 的抽象类,重写父类的 run的方法. 2.run() 与start()的区别 调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行. 把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的.并且run()方法必须是public访问权限,返回值

QT多线程笔记

1.QT多线程涉及到主线程和子线程之间交互大量数据的时候,使用QThread并不方便,因为run()函数本身不能接受任何参数,因此只能通过信号和槽的交互来获取数据,如果只是单方面简单交互数据还过得去,如果涉及多次主.子线程之间的交互的时候,这种方式是很不方便的,这里采取另外一种方式,建一个继承自QObject的类,进行movetoThread()操作: class FileThread :public QObject { Q_OBJECT public: QString m_path; QFil

Java基础知识强化之多线程笔记01:多线程基础知识(详见Android(java)笔记61~76)

1. 基础知识: Android(java)学习笔记61:多线程程序的引入    ~    Android(java)学习笔记76:多线程-定时器概述和使用 

Java多线程笔记

1.并发通常可以提高单处理器上程序的性能 其实,在单处理器上并发的执行程序锁用的开销大于顺序执行.然而顺序执行时,程序有时会因为某些条件(通常是I/O问题)导致不能继续执行,称为线程阻塞,如果没有并发,程序将停止不前.而使用了并发,一个任务阻塞,其他任务还可以继续执行,这就保证了程序的完成.所以,如果确定没有任务会出现阻塞,在单处理器上并发执行程序是不必要的. 2.并发在单处理器上性能提高最常见的实例是--事件驱动的编程 并发最吸引人的额就是可以产生可响应的用户界面,比如某个程序将长期的运行某个

多线程笔记--原子操作Interlocked系列函数

前面写了一个多线程报数的功能,为了描述方便和代码简洁起见,只输出最后的报数结果来观察程序运行结果.这非常类似一个网站的客户访问统计,每个用户登录用一个线程模拟,线程运行时将一个表示计数的变量递增.程序在最后输出这个计数的值表示今天有多少用户登录.如果这个值不等于我们启动的线程个数,那这个程序就是有问题的. #include <stdio.h> #include <process.h> #include <Windows.h> volatile long g_nLogin

多线程笔记 - day01

1.基本概念 01-进程 进程是指在系统中正在运行的一个运行程序.每个进程之间是独立的,每个进程均在其专用且受保护的内存空间内. 02-线程 2-1 基本概念 1进程要执行任务,一定要有线程(每个进程至少一个线程),线程是进程的基本执行单位,一个进程(程序)的所有任务都在线程中执行. 2-2 线程的串行 1个线程中任务的执行是串行的,如果要在1个线程中执行多个任务,那么只能一个一个地按顺序执行这些任务.也就是说,在同一时间内,1个线程只能执行1个任务. 03 多线程 3-1 基本概念 即1个进程

java多线程笔记(二)

      java多线程的难点是在:处理多个线程同步与并发运行时线程间的通信问题.java在处理线程同步时,常用方法有: 1.synchronized关键字. 2.Lock显示加锁. 3.信号量Semaphore.   线程同步问题引入:       创建一个银行账户Account类,在创建并启动100个线程往同一个Account类实例里面添加一块钱.在没有使用上面三种方法的情况下: 代码: import java.util.concurrent.ExecutorService; import

java 多线程笔记

一.先简单粗暴解释一下一些与线程有关的概念 1.并行与并发 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时. 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时. 2.资源共享 多个线程调用资源,是同一个或多个资源. 3.线程安全 在并发的情况之下,代码经过多线程使用,线程的调度顺序不影响最后结果,则是线程安全的. 3.同步 Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全.例如使用@synchronized.

多线程笔记小记

多线程编程:1. 调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor(锁.监视器),然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁):2. 如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法). 调用某个对象的noti

Java基础知识强化之多线程笔记05:Java中继承thread类 与 实现Runnable接口的区别

1. Java中线程的创建有两种方式:  (1)通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中. (2)通过实现Runnable接口,实例化Thread类. 2. 在实际应用中,我们经常用到多线程,如车站的售票系统,车站的各个售票口相当于各个线程.当我们做这个系统的时候可能会想到两种方式来实现,继承Thread类或实现Runnable接口,现在看一下这两种方式实现的两种结果. 继承thread类 1 package com.threadtest; 2 clas