VC多线程通信(详解及实例)

VC多线程通信(详解及实例)
VC多线程通信(详解及实例)

VC中利用多线程技术实现线程之间的通信

当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的大型应用软件无一不是多线程多任务处理,单线程的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本实例针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,分别进行探讨,并利用多线程技术进行线程之间的通信,实现了数字的简单排序。

一、实现方法

1、理解线程

要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应于Visual C++中的CwinThread类对象。单独一个执行程序运行时,缺省地包含的一个主线程,主线程以函数地址的形式出现,提供程序的启动点,如main()或WinMain ()函数等。当主线程终止时,进程也随之终止。根据实际需要,应用程序可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。

一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。

线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对象就是一个用

户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进程终止。工作线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从CWinThread类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。

2、线程的管理和操作

(一)线程的启动

创建一个用户界面线程,首先要从类CwinThread产生一个派生类,同时必须使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现这个CwinThread派生类。第二步是根据需要重载该派生类的一些成员函数如:ExitInstance()、InitInstance ()、OnIdle()、PreTranslateMessage()等函数。最后调用AfxBeginThread()函数的一个版本:CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ) 启动该用户界面线程,其中第一个参数为指向定义的用户界面线程类指针变量,第二个参数为线程的优先级,第三个参数为线程所对应的堆栈大小,第四个参数为线程创建时的附加标志,缺省为正常状态,如为CREATE_SUSPENDED则线程启动后为挂起状态。

对于工作线程来说,启动一个线程,首先需要编写一个希望与应用程序的其余部分并行运行的函数如Fun1(),接着定义一个指向CwinThread对象的指针变量*pThread,调用AfxBeginThread(Fun1,param,priority)函数,返回值赋给pThread变量的同时一并启动该线程来执行上面的Fun1()函数,其中Fun1是线程要运行的函数的名字,也既是上面所说的控制函数的名字,param是准备传送给线程函数Fun1的任意32位值,priority则是定义该线程的优先级别,它是预定义的常数,读者可参考MSDN。

(二)线程的优先级

以下的CwinThread类的成员函数用于线程优先级的操作:

int GetThreadPriority();

BOOL SetThradPriority()(int nPriority);

上述的二个函数分别用来获取和设置线程的优先级,这里的优先级,是相对于该线程所处的优先权层次而言的,处于同一优先权层次的线程,优先级高的线程先运行;处于不同优先权层次上的线程,谁的优先权层次高,谁先运行。至于优先级设置所需的常数,自己参考MSDN就可以了,要注意的是要想设置线程的优先级,这个线程在创建时必须具有

THREAD_SET_INFORMATION访问权限。对于线程的优先权层次的设置,CwinThread类没有提供相应的函数,但是可以通过Win32 SDK函数GetPriorityClass()和SetPriorityClass ()来实现。

(三)线程的悬挂和恢复

CWinThread类中包含了应用程序悬挂和恢复它所创建的线程的函数,其中SuspendThread()用来悬挂线程,暂停线程的执行;ResumeThread()用来恢复线程的执行。如果你对一个线程连续若干次执行SuspendThread(),则需要连续执行相应次的ResumeThread()来恢复线程的运行。

(四)结束线程

终止线程有三种途径,线程可以在自身内部调用AfxEndThread()来终止自身的运行;可以在线程的外部调用BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )来强行终止一个线程的运行,然后调用CloseHandle()函数释放线程所占用的堆栈;第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。下面以第三种方法为例,给出部分代码:

////////////////////////////////////////////////////////////////

//////CtestView message handlers

/////Set to True to end thread

Bool bend=FALSE;//定义的全局变量,用于控制线程的运行;

//The Thread Function;

UINT ThreadFunction(LPVOID pParam)//线程函数

{

while(!bend)

{

Beep(100,100);

Sleep(1000);

}

return 0;

}

/////////////////////////////////////////////////////////////

CwinThread *pThread;

HWND hWnd;

Void CtestView::OninitialUpdate()

{

hWnd=GetSafeHwnd();

pThread=AfxBeginThread(ThradFunction,hWnd);//启动线程

pThread->m_bAutoDelete=FALSE;//线程为手动删除

Cview::OnInitialUpdate();

}

////////////////////////////////////////////////////////////////

Void CtestView::OnDestroy()

{

bend=TRUE;//改变变量,线程结束

WaitForSingleObject(pThread->m_hThread,INFINITE);//等待线程结束delete pThread;//删除线程

Cview::OnDestroy();

}

3、线程之间的通信

通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件对象、使用消息。这里我们主要介绍后两种方法。

(一)利用用户定义的消息通信

在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息,如下所示:#define WM_USERMSG WMUSER+100;在需要的时候,在一个线程中调用::PostMessage((HWND)param,WM_USERMSG,0,0)或

CwinThread::PostThradMessage()来向另外一个线程发送这个消息,上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码是对上节代码的修改,修改后的结果是在线程结束时显示一个对话框,提示线程结束:

UINT ThreadFunction(LPVOID pParam)

{

while(!bend)

{

Beep(100,100);

Sleep(1000);

}

::PostMessage(hWnd,WM_USERMSG,0,0);

return 0;

}

////////WM_USERMSG消息的响应函数为OnThreadended(WPARAM wParam, LPARAM lParam)

LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam)

{

AfxMessageBox("Thread ended.");

Retrun 0;

}

上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息,让它在后台完成。在控制函数中可以直接使用::GetMessage()这个SDK函数进行消息分检和处理,自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时,线程将系统分配给它的时间片让给其它线程,不无效的占用CPU的时间,如果消息队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。

(二)用事件对象实现通信

在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用MFC的Cevent类的对象来表示。事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。上述例子代码修改如下:

////////////////////////////////////////////////////////////////////

Cevent threadStart ,threadEnd;

UINT ThreadFunction(LPVOID pParam)

{

::WaitForSingleObject(threadStart.m_hObject,INFINITE);

AfxMessageBox("Thread start.");

while(!bend)

{

Beep(100,100);

Sleep(1000);

Int result=::WaitforSingleObject(threadEnd.m_hObject,0);

//等待threadEnd事件有信号,无信号时线程在这里悬停

If(result==Wait_OBJECT_0)

Bend=TRUE;

}

::PostMessage(hWnd,WM_USERMSG,0,0);

return 0;

}

/////////////////////////////////////////////////////////////

Void CtestView::OninitialUpdate()

{

hWnd=GetSafeHwnd();

threadStart.SetEvent();//threadStart事件有信号

pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程

pThread->m_bAutoDelete=FALSE;

Cview::OnInitialUpdate();

}

////////////////////////////////////////////////////////////////

Void CtestView::OnDestroy()

{

threadEnd.SetEvent();

WaitForSingleObject(pThread->m_hThread,INFINITE);

delete pThread;

Cview::OnDestroy();

}

运行这个程序,当关闭程序时,才显示提示框,显示"Thread ended"。

4、线程之间的同步

前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题,

MFC支持的七个多线程的同步类可以分成两大类:同步对象(CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步访问对象(CmultiLock和CsingleLock)。本节主要介绍临界区(critical section)、互斥(mutexe)、信号量(semaphore),这些同步对象使各个线程协调工作,程序运行起来更安全。

(一)临界区

临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。临界区对应着一个CcriticalSection对象,当线程需要访问保护数据时,调用临界区对象的Lock()成员函数;当对保护数据的操作完成之后,调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程,它们对应的函数分别为WriteThread()和ReadThread(),用以对公共数组组array[]操作,下面的代码说明了如何使用临界区对象:

#include "afxmt.h"

int array[10],destarray[10];

CCriticalSection Section;

UINT WriteThread(LPVOID param)

{

Section.Lock();

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

array[x]=x;

Section.Unlock();

}

UINT ReadThread(LPVOID param)

{

Section.Lock();

For(int x=0;x<10;x++)

Destarray[x]=array[x];

Section.Unlock();

}

上述代码运行的结果应该是Destarray数组中的元素分别为1-9,而不是杂乱无章的数,如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下。

(二)互斥

互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。互斥与Cmutex类的对象相对应,使用互斥对象时,必须创建一个CSingleLock或CMultiLock对象,用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用CSingleLock对象,该对象的Lock()函数用于占有互斥,Unlock()用于释放互斥。实现代码如下:

#include "afxmt.h"

int array[10],destarray[10];

CMutex Section;

UINT WriteThread(LPVOID param)

{

CsingleLock singlelock;

singlelock (&Section);

singlelock.Lock();

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

array[x]=x;

singlelock.Unlock();

}

UINT ReadThread(LPVOID param)

{

CsingleLock singlelock;

singlelock (&Section);

singlelock.Lock();

For(int x=0;x<10;x++)

Destarray[x]=array[x];

singlelock.Unlock();

}

(三)信号量

信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,创建一个信号量需要用Csemaphore类声明一个对象,一旦创建了一个信号量对象,就可以用它来对资源的访问技术。要实现计数处理,先创建一个CsingleLock或CmltiLock对象,然后用该对象的Lock()函数减少这个信号量的计数值,Unlock()反之。下面的代码分别启动三个线程,执行时同时显示二个消息框,然后10秒后第三个消息框才得以显示。

/////////////////////////////////////////////////////////////////////////

Csemaphore *semaphore;

Semaphore=new Csemaphore(2,2);

HWND hWnd=GetSafeHwnd();

AfxBeginThread(threadProc1,hWnd);

AfxBeginThread(threadProc2,hWnd);

AfxBeginThread(threadProc3,hWnd);

UINT ThreadProc1(LPVOID param)

{

CsingleLock singelLock(semaphore);

singleLock.Lock();

Sleep(10000);

::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK);

return 0;

}

UINT ThreadProc2(LPVOID param)

{

CSingleLock singelLock(semaphore);

singleLock.Lock();

Sleep(10000);

::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK);

return 0;

}

UINT ThreadProc3(LPVOID param)

{

CsingleLock singelLock(semaphore);

singleLock.Lock();

Sleep(10000);

::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK);

return 0;

}

二、编程步骤

1、启动Visual C++6.0,生成一个32位的控制台程序,将该程序命名为"sequence"

2、输入要排续的数字,声明四个子线程;

3、输入代码,编译运行程序。

三、程序代码

//////////////////////////////////////////////////////////////////////////////////////

// sequence.cpp : Defines the entry point for the console application.

/*

主要用到的WINAPI线程控制函数,有关详细说明请查看MSDN;

线程建立函数:

HANDLE CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes, // 安全属性结构指针,可为NULL;

DWORD dwStackSize, // 线程栈大小,若为0表示使用默认值;

LPTHREAD_START_ROUTINE lpStartAddress, // 指向线程函数的指针;

LPVOID lpParameter, // 传递给线程函数的参数,可以保存一个指针值;

DWORD dwCreationFlags, // 线程建立是的初始标记,运行或挂起;

LPDWORD lpThreadId // 指向接收线程号的DWORD变量;

);

对临界资源控制的多线程控制的信号函数:

HANDLE CreateEvent(

LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性结构指针,可为NULL;

BOOL bManualReset, // 手动清除信号标记,TRUE在WaitForSingleObject后必须手动//调用RetEvent清除信号。若为FALSE则在WaitForSingleObject //后,系统自动清除事件信号;

BOOL bInitialState, // 初始状态,TRUE有信号,FALSE无信号;

LPCTSTR lpName // 信号量的名称,字符数不可多于MAX_PATH;

//如果遇到同名的其他信号量函数就会失败,如果遇

//到同类信号同名也要注意变化;

);

HANDLE CreateMutex(

LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性结构指针,可为NULL BOOL bInitialOwner, // 当前建立互斥量是否占有该互斥量TRUE表示占有,

//这样其他线程就不能获得此互斥量也就无法进入由

//该互斥量控制的临界区。FALSE表示不占有该互斥量

LPCTSTR lpName // 信号量的名称,字符数不可多于MAX_PATH如果

//遇到同名的其他信号量函数就会失败,

//如果遇到同类信号同名也要注意变化;

);

//初始化临界区信号,使用前必须先初始化

VOID InitializeCriticalSection(

LPCRITICAL_SECTION lpCriticalSection // 临界区变量指针

);

//阻塞函数

//如果等待的信号量不可用,那么线程就会挂起,直到信号可用

//线程才会被唤醒,该函数会自动修改信号,如Event,线程被唤醒之后//Event信号会变得无信号,Mutex、Semaphore等也会变。DWORD WaitForSingleObject(

HANDLE hHandle, // 等待对象的句柄

DWORD dwMilliseconds // 等待毫秒数,INFINITE表示无限等待);

//如果要等待多个信号可以使用WaitForMutipleObject函数

*/

#include "stdafx.h"

#include "stdlib.h"

#include "memory.h"

HANDLE evtTerminate; //事件信号,标记是否所有子线程都执行完

/*

下面使用了三种控制方法,你可以注释其中两种,使用其中一种。

注意修改时要连带修改临界区PrintResult里的相应控制语句

*/

HANDLE evtPrint; //事件信号,标记事件是否已发生

//CRITICAL_SECTION csPrint; //临界区

//HANDLE mtxPrint; //互斥信号,如有信号表明已经有线程进入临界区并拥有此信号

static long ThreadCompleted = 0;

/*用来标记四个子线程中已完成线程的个数,当一个子线程完成时就对ThreadCompleted进行加一操作, 要使用InterlockedIncrement(long* lpAddend)和InterlockedDecrement(long* lpAddend)进行加减操作*/

//下面的结构是用于传送排序的数据给各个排序子线程

struct MySafeArray

{

long* data;

int iLength;

};

//打印每一个线程的排序结果

void PrintResult(long* Array, int iLength, const char* HeadStr = "sort");

//排序函数

unsigned long __stdcall BubbleSort(void* theArray); //冒泡排序

unsigned long __stdcall SelectSort(void* theArray); //选择排序

unsigned long __stdcall HeapSort(void* theArray); //堆排序

unsigned long __stdcall InsertSort(void* theArray); //插入排序

/*以上四个函数的声明必须适合作为一个线程函数的必要条件才可以使用CreateThread

建立一个线程。

(1)调用方法必须是__stdcall,即函数参数压栈顺序由右到左,而且由函数本身负责

栈的恢复, C和C++默认是__cdecl, 所以要显式声明是__stdcall

(2)返回值必须是unsigned long

(3)参数必须是一个32位值,如一个指针值或long类型

(4) 如果函数是类成员函数,必须声明为static函数,在CreateThread时函数指针有特殊的写法。如下(函数是类CThreadTest的成员函数中):

static unsigned long _stdcall MyThreadFun(void* pParam);

handleRet = CreateThread(NULL, 0, &CThreadTestDlg::MyThreadFun, NULL, 0, &ThreadID);

之所以要声明为static是由于,该函数必须要独立于对象实例来使用,即使没有声明实例也可以使用。*/

int QuickSort(long* Array, int iLow, int iHigh); //快速排序

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

{

long data[] = {123,34,546,754,34,74,3,56};

int iDataLen = 8;

//为了对各个子线程分别对原始数据进行排序和保存排序结果

//分别分配内存对data数组的数据进行复制

long *data1, *data2, *data3, *data4, *data5;

MySafeArray StructData1, StructData2, StructData3, StructData4;

data1 = new long[iDataLen];

memcpy(data1, data, iDataLen << 2); //把data中的数据复制到data1中

//内存复制memcpy(目标内存指针, 源内存指针, 复制字节数), 因为long的长度

//为4字节,所以复制的字节数为iDataLen << 2, 即等于iDataLen*4

StructData1.data = data1;

StructData1.iLength = iDataLen;

data2 = new long[iDataLen];

memcpy(data2, data, iDataLen << 2);

StructData2.data = data2;

StructData2.iLength = iDataLen;

data3 = new long[iDataLen];

memcpy(data3, data, iDataLen << 2);

StructData3.data = data3;

StructData3.iLength = iDataLen;

data4 = new long[iDataLen];

memcpy(data4, data, iDataLen << 2);

StructData4.data = data4;

StructData4.iLength = iDataLen;

data5 = new long[iDataLen];

memcpy(data5, data, iDataLen << 2);

unsigned long TID1, TID2, TID3, TID4;

//对信号量进行初始化

evtTerminate = CreateEvent(NULL, FALSE, FALSE, "Terminate");

evtPrint = CreateEvent(NULL, FALSE, TRUE, "PrintResult");

//分别建立各个子线程

CreateThread(NULL, 0, &BubbleSort, &StructData1, NULL, &TID1);

CreateThread(NULL, 0, &SelectSort, &StructData2, NULL, &TID2);

CreateThread(NULL, 0, &HeapSort, &StructData3, NULL, &TID3);

CreateThread(NULL, 0, &InsertSort, &StructData4, NULL, &TID4);

//在主线程中执行行快速排序,其他排序在子线程中执行

QuickSort(data5, 0, iDataLen - 1);

PrintResult(data5, iDataLen, "Quick Sort");

WaitForSingleObject(evtTerminate, INFINITE); //等待所有的子线程结束

//所有的子线程结束后,主线程才可以结束

delete[] data1;

delete[] data2;

delete[] data3;

delete[] data4;

CloseHandle(evtPrint);

return 0;

}

/*

冒泡排序思想(升序,降序同理,后面的算法一样都是升序):从头到尾对数据进行两两比较进行交换,小的放前大的放后。这样一次下来,最大的元素就会被交换的最后,然后下一次

循环就不用对最后一个元素进行比较交换了,所以呢每一次比较交换的次数都比上一次循环的次数少一,这样N次之后数据就变得升序排列了*/

unsigned long __stdcall BubbleSort(void* theArray)

{

long* Array = ((MySafeArray*)theArray)->data;

int iLength = ((MySafeArray*)theArray)->iLength;

int i, j=0;

long swap;

for (i = iLength-1; i >0; i--)

{

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

{

if(Array[j] >Array[j+1]) //前比后大,交换

{

swap = Array[j];

Array[j] = Array[j+1];

Array[j+1] = swap;

}

}

}

PrintResult(Array, iLength, "Bubble Sort"); //向控制台打印排序结果

InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1

if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完//若都执行完则设置程序结束信号量

return 0;

}

/*选择排序思想:每一次都从无序的数据中找出最小的元素,然后和前面已经有序的元素序列的后一个元素进行交换,这样整个源序列就会分成两部分,前面一部分是已经排好序的有序序列,后面一部分是无序的,用于选出最小的元素。循环N次之后,前面的有序序列加长到跟源序列

一样长,后面的无序部分长度变为0,排序就完成了。*/

unsigned long __stdcall SelectSort(void* theArray)

{

long* Array = ((MySafeArray*)theArray)->data;

int iLength = ((MySafeArray*)theArray)->iLength;

long lMin, lSwap;

int i, j, iMinPos;

for(i=0; i < iLength-1; i++)

{

lMin = Array[i];

iMinPos = i;

for(j=i + 1; j <= iLength-1; j++) //从无序的元素中找出最小的元素

{

if(Array[j] < lMin)

{

iMinPos = j;

lMin = Array[j];

}

}

//把选出的元素交换拼接到有序序列的最后

lSwap = Array[i];

Array[i] = Array[iMinPos];

Array[iMinPos] = lSwap;

}

PrintResult(Array, iLength, "Select Sort"); //向控制台打印排序结果

InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1

if(ThreadCompleted == 4) SetEvent(evtTerminate);//检查是否其他线程都已执行完//若都执行完则设置程序结束信号量

return 0;

}

/*堆排序思想:堆:数据元素从1到N排列成一棵二叉树,而且这棵树的每一个子树的根都是该树中的元素的最小或最大的元素这样如果一个无序数据集合是一个堆那么,根元素就是最小或最大的元素堆排序就是不断对剩下的数据建堆,把最小或最大的元素析透出来。下面的算法,就是从最后一个元素开始,依据一个节点比父节点数值大的原则对所有元素进行调整,这样调整一次就形成一个堆,第一个元素就是最小的元素。然后再对剩下的无序数据再进行建堆,注意这时后面的无序数据元素的序数都要改变,如第一次建堆后,第二个元素就会变成堆的第一个元素。*/

unsigned long __stdcall HeapSort(void* theArray)

{

long* Array = ((MySafeArray*)theArray)->data;

int iLength = ((MySafeArray*)theArray)->iLength;

int i, j, p;

long swap;

for(i=0; i{

for(j = iLength - 1; j>i; j--) //从最后倒数上去比较字节点和父节点

{

p = (j - i - 1)/2 + i; //计算父节点数组下标

//注意到树节点序数跟数组下标不是等同的,因为建堆的元素个数逐个递减

if(Array[j] < Array[p]) //如果父节点数值大则交换父节点和字节点

{

swap = Array[j];

Array[j] = Array[p];

Array[p] = swap;

}

}

}

PrintResult(Array, iLength, "Heap Sort"); //向控制台打印排序结果InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1

if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完

//若都执行完则设置程序结束信号量

return 0;

}

/*插入排序思想:把源数据序列看成两半,前面一半是有序的,后面一半是无序的,把无序的数据从头到尾逐个逐个的插入到前面的有序数据中,使得有序的数据的个数不断增大,同时无序的数据个数就越来越少,最后所有元素都会变得有序。*/

unsigned long __stdcall InsertSort(void* theArray)

{

long* Array = ((MySafeArray*)theArray)->data;

int iLength = ((MySafeArray*)theArray)->iLength;

int i=1, j=0;

long temp;

for(i=1; i{

temp = Array[i]; //取出序列后面无序数据的第一个元素值

for(j=i; j>0; j--) //和前面的有序数据逐个进行比较找出合适的插入位置

{

if(Array[j - 1] >temp) //如果该元素比插入值大则后移

Array[j] = Array[j - 1];

else //如果该元素比插入值小,那么该位置的后一位就是插入元素的位置

break;

}

Array[j] = temp;

}

PrintResult(Array, iLength, "Insert Sort"); //向控制台打印排序结果

InterlockedIncrement(&ThreadCompleted); //返回前使线程完成数标记加1

if(ThreadCompleted == 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完//若都执行完则设置程序结束信号量

return 0;

}

多线程技术在数据通信中的应用

多线程技术在数据通信中的应用 发表时间:2016-12-14T09:50:36.467Z 来源:《基层建设》2016年22期作者:黄华[导读] 摘要:随着信息科学技术的突飞猛进,人们社会已经进入“信息化时代”,大量先进的信息科学技术被人们广泛地应用到各行各业中,并转化为先进的生产力。 身份证号:45032519860724**** 广西南宁 530000 摘要:随着信息科学技术的突飞猛进,人们社会已经进入“信息化时代”,大量先进的信息科学技术被人们广泛地应用到各行各业中,并转化为先进的生产力。尤其,数据通信中多线程技术的应用既能进一步提升数据通信的应用效果,又能很好的满足人类对数据通信的需求。文章介绍了多线程技术相关知识,探讨了多线程技术在数据通信中的实际应用,希望对数据通信有所帮助。 关键词:多线程技术;数据通信;应用 一、绪论 一般情况下,相关技术人员在设计数据通信软件系统的过程当中,它的硬件设施绝大部分均需要与远程设备进行通信处理,而这种通信处理重点通过通信信道自一端往另外一端发出指令进而实现数据信息的有效传输。值得注意的是,这些数据信息在传输的过程当中需要一定时间的延迟。所以,技术人员在设计数据通信软件系统的过程当中,需在整个通信软件系统内部设计出一个循环系统,尽可能地克服延时现象,进而确保整个通信软件系统的正常、高效运转。多线程技术在数据通信中的应用正好能有效的解决这些难题,能够有效的提升数据通信的安全性与高效性。 二、多线程技术及适用场合 多线程技术的实质为在整个通信系统程序当中具有三个或三个以上的线程来共同负责用户信息的输入。多线程技术在数据通信的实际应用当中非常重要,尤其需要特别注意多线程技术的适用场合,不可盲目,为此,需要在设计多线程技术模型的过程当中,重点考虑下边三个问题:一是必需有一个能够等候用户输入信息的主循环程序;二是必需有一个能够为整个通信系统提供用户输入信息处理的模块;三是必需有一条规范的保证数据通信运行正常的机制,以确保用户在数据信息的输入过程当中,通过数据通信系统内部的主循环程序确保工作的正常使用与高效运转。 为此,在数据通信系统正常运转的过程当中,倘若处理的数据信息程序较为繁杂,则可应用多线程技术来实现繁杂数据通信的高效处理,尤其能够同时处理用户输入的大量数据信息,极大的提高了处理的效率,也大大的缩短了用户输入数据信息的延迟时间。此外,多线程技术应用在数据通信过程当中,如果一个用户在输入信息的过程中自身并没有一个相应的模块,那么该系统也会通过整个系统的自动检索为用户提供其他用户相似的处理方式,通过数据通信系统最为关键的主程序循环系统进行全面的调度,帮助用户实现数据信息处理的优先性。 三、数据通信与多线程技术系统 通常情况下,在“OSI开放系统互连”栈式结构中具有一组协议,该组协议中物理层处于最低层,其主要承担数据的传输。而该组协议中的应用层为顶层,其功能主要是负责与用户的对接工作。例如,在一台电子计算机中,低层的物理层承担着将一端的数据信息传送到另外一端的链条上,从而确保数据信息能够从一端传输到另外一端的对等上。当数据信息传送完成以后,低层的物理层则处在待命状态,等待其他对等面的数据信息的传输。需要注意的是,低层的物理层并不会由于正在运行指令而而拒绝另外一个指令。为此,低层物理层的运转正是与多线程技术相吻合的。 四、多线程技术在数据通信中的应用 (一)多线程技术应用于数据通信的编程要素。 在数据通信系统当中应用多线程技术进行编程设计,必须熟练、准确掌握多线程技术有关技术要素:一是主循环。主循环也被称为主事件循环,其主要负责传送与接收事件,与此同时,主循环还承担着调度功能。二是向主循环通知事件,也就是为主循环产生事件模块。三是主事件循环通知它所发生的事件,也就是接收通知模块,接收通知模块也被人们叫做数据处理器。四是使主事件循环能够知道所有它需要监控的事件的机制。为此,每一个Eventhandler则能够及时通知主事件循环其需哪些事件。 (二)多线程技术应用于数据通信的编程设计 多线程技术在数据通信中的有效运用主要是通过编程设计去实现,具体的设计主要包括以下两个方面: 1.设计框。设计框包括主循环的编程设计、事件处理程序的编程设计以及事件处理程序子类的编程设计等方面。 2.主要操作。多线程技术应用于数据通信的主要操作程序如下表所示: 以上操作程序根据国际有关标准执行,不仅提升数据通信系统的准确性,而且保证了数据通信的安全性,与此同时,还大大的降低了数据通信系统维护的难度,操作性非常强,极大的提高了工作效率。 五、小结 在管理数据通信系统过程当中,为了进一步提高网络管理成效,保证其正常、高效的运转,尽量克服延时现象,人们可以应用多线程技术进而有效的确保数据通信的正常、高效运行。尤其,在数据通信中应用多线程技术,需要了解多线程技术及适用场合,并且熟练掌握多线程技术应用于数据通信的编程要素、编程设计等,从而有效的解决编程设计在数据通信实际应用过程中存在的有关问题。参考文献 [1]费翔林.多线程技术的研究与应用[J].计算机研究与发展.2000(04) [2]周兴铭.多线程技术的现状与前景展望[J].计算机工程与科学.2009(08) [3]刘爽.基于TCP/IP协议和多线程的通信软件的设计与实现[J].计算机工程与设计.2010(04) [4]伍光胜.多线程技术及其应用的研究[J].计算机应用研究.2010(01)

word操作实例

教育部教育管理信息中心 全国“信息技术及应用培训”教育工程试题 《 办公自动化 》类试卷(B )卷 考试时间:180分钟 类(课程)名称:word excel PowerPoint internet 基地名称: 江苏省泰州市海陵区时代电脑培训中心 理论题部分( 40 分) 一、选择题(每题2分,共30分) 1. Word 具有的功能是( )。 A. 表格处理 B. 绘制图形 C. 自动更正 D. 以上三项都是 2. 下列选项不属于Word 窗口组成部分的是( )。 A. 标题栏 B. 对话框 C. 菜单栏 D. 状态栏 3. 在Word 编辑状态下,若要在当前窗口中打开(关闭)绘图工具栏,则可选择的操作是( )。 A .单击“工具”一“绘图” B .单击“视图”一“绘图” C .单击“编辑”一“工具栏” 一“绘图” D .单击“视图”一“工具栏” 一“绘图” 4. 在Word 编辑状态下,若要进行字体效果的设置(如上、下标等),首先应打开( )。 A.“编辑”下拉菜单 B.“视图”下拉菜单 C.“格式”下拉菜单 D.“工具”下拉菜单 5. 在Word 编辑状态下,若光标位于表格外右侧的行尾处,按Enter(回车)键,结果( )。 A .光标移到下一列 B. 光标移到下一行,表格行数不变。 C .插入一行,表格行数改变。 D. 在本单元格内换行,表格行数不变。 6.在Word 编辑状态下,对于选定的文字( )。 A .可以设置颜色,不可以设置动态效果。 B .可以设置动态效果,不可以设置颜色。 准考证号: 身份证号: 姓名:

C.既可以设置颜色,也可以设置动态效果。 D.不可以设置颜色,也不可以设置动态效果。 7. 图文混排是Word的特色功能之一,以下叙述中错误的是( )。 A.可以在文档中插入剪贴画 B. 可以在文档中插入图形 C.可以在文档中使用文本框 D. 可以在文档中使用配色方案 8. 在Word 中,新建一个Word文档,默认的文件名是“文档1”,文档内容的第一行标题是“说明书”,对该文件保存时没有重新命名,则该Word文档的文件名是( )。 A.文档1.doc B. doc1.doc C.说明书.doc D. 没有文件名 9. 在Word编辑状态下,若想将表格中连续三列的列宽调整为1厘米,应该先选中这三列,然后单击( )。 A.“表格”→“平均分布各列” B.“表格”→ “表格属性” C.“表格”→“表格自动套用格式” D.“表格”→“平均分布各行” 10. 在Word 中,下述关于分栏操作的说法,正确的是( )。 A. 可以将指定的段落分成指定宽度的两栏 B. 任何视图下均可看到分栏效果 C. 设置的各栏宽度和间距与页面宽度无关 D. 栏与栏之间不可以设置分隔线 11. 在Excel工作簿中,有关移动和复制工作表的说法正确的是( )。 A.工作表只能在所在工作簿内移动不能复制 B.工作表只能在所在工作簿内复制不能移动 C.工作表可以移动到其它工作簿内,不能复制到其它工作簿内。 D.工作表可以移动到其它工作簿内,也可复制到其它工作簿内。 12. 在Excel工作表的某单元格内,输入数字字符串“456”,正确的输入方式是()。A.456 B. '456 C. =456 D. "456" 13.在Excel中,关于工作表及为其建立的嵌入式图表的说法,正确的是()。 A.删除工作表中的数据,图表中的数据系列不会删除。 B.增加工作表中的数据,图表中的数据系列不会增加。 C.修改工作表中的数据,图表中的数据系列不会修改。 D.以上三项均不正确 14.在打印工作表前,就能看到实际打印效果的操作是()。 A.仔细观察打印表 B. 打印预览C.按F8键 D. 分页预览 15.在Excel工作表中,选定某单元格,单击“编辑”菜单下的“删除”选项,不可能完成的操作是( )。 A. 删除该行 B. 右侧单元格左移 C. 删除该列 D. 左侧单元格右移

CE傻瓜教程全九课

CE傻瓜教程一:基本操作 先简单介绍下什么叫CE,CE的全称是Cheat Engine,最新的版本是(作者是 Dark Byte)CE是目前最优秀的游戏修改器,不是之一,这个工具绝对值得你去学习(只要花一点时间就够了)。 忘记金山游侠,GM8,FPE之类的修改工具的吧,CE会让你爱不释手。 一、先下载CE ,这个汉化版相当不错哦(不需要安装),推荐各位下载使用。 二、打开CE目录下的2个文件: 三、附加进程(图示): CE傻瓜教程二:精确数值扫描

接着第一关的操作 按下一步进入教程第二关,需要扫描的精确数值是100 现在开始搜索精确数值 100 数值中输入100点击首次扫描按钮 一般游戏就是4字节,这里不需要改动,默认就好。 这次扫描我们得到 59 个结果,里面肯定有我们要找的那个血值,不过好像太多了。

关键一步:回到 Tutorial 点击打我按钮,此时血值已有变化了: 我们再输入 96 点击再次扫描按钮结果只剩1个(这就是我们要找的),我们双击此地址将其添加到地址栏: 只有1个结果了,这个就是我们要找的内存地址,双击将其加入到地址栏

图示操作: 把 95 改成 1000 点击确定按钮

此时教程的下一步按钮变成可用 闯关成功。 操作虽然简单,但是大家需要明白这其实是一个筛选的过程,这样操作就能把地址找出来。本关的小技巧: 1、双击下图对应位置可快速更改数值。 2、双击地址可快速将其加入到地址栏 CE傻瓜教程三:未知初始数值 第3关的密码是 419482

这一关很重要,因为某些游戏中血显示的不是数字而是血条,这样的话教程2中的方法就失效了。 本关就你要教会你如何修改这些讨厌的未知数 此时点击新扫描然后选择未知初始数值 点击首次扫描然后出现了肯定是N多的结果,因为太多了,CE没有显示出来。 老办法,回到 Tutorial ,点击打我 ,CE会告诉你血量减了多少,比如-1

C#多线程通讯

实现线程通讯的例子有很多种,我们这里介绍几个简单的例子给大家看 第二篇我们对线程进行了简单的分工,但是存在一个问题,小B并不知道小A 的工作完成了没有,并且小B线程需要在小A的工作完成之后才能接着做下面的工作,但是小A在机子的办公室里懒得出来,他只在办公室里说我的工作完成了,但是小B小C都不知道,小B和小C在自己的办公室里听不到小A说的话(我们把每个线程比作一个独立的办公室),如此下来,小B和小C只能闲置下来,我们想办法要让小B和小C知道小A的工作已经完成了! 老板急了,这个时候需要考虑发工资的问题,让人闲着的话是巨大的人力资源和财力的浪费,老板就想这个时候我们最好的办法就是给成员们的办公室里办个电话,可以互相通电话,电话总机放在我这里,为了避免成员偷懒,我让总机把他们的工作状态都记录下来,给成员们每个动作都进行了侦听,方便高效的考核和发工资。 老板这人比较更懒,想一下上面的方法虽然可以解决他们偷懒的问题,但是每次让我去问太麻烦了,我更懒得一个电话一个电话的问,算了,恶心他们,通知小A,如果你工作做完了,就通知总机,小B和小C定时打给总机问小A是否完成了工作,那么我们就通过电话总机的自动应答来协调A,B,C之间的工作吧 这样代码就很简单了,我们这样定义 using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace CrazyCoder.多线程教程 { public class老板 { private static List<员工> _电话总机记录; /// ///各个员工的工作状态 /// public static List<员工> 电话总机记录 { get { return _电话总机记录; } set { _电话总机记录= value; } }

多线程技术在Android手机开发中的运用

龙源期刊网 https://www.360docs.net/doc/bf18848676.html, 多线程技术在Android手机开发中的运用 作者:谢光刘志惠 来源:《电子技术与软件工程》2017年第24期 摘要 在Android手机开发过程中,一般情况下程序是通过一个线程进行工作的,因此当一个任务耗费过长时间,就会造成主程序无响应并对程序运行的顺畅程度造成影响的问题。基于此,本文通过对多线程组成进行介绍,在Android中多线程技术模块与具体实现方式两方面对多线程技术在安卓手机开发中的运用进行探讨,以为关注此问题的人们提供参考。 【关键词】多线程技术 Android手机进程线程 安卓系统自2007年由谷歌公司开发后,得到了巨大的发展。截至2017年3月,其市场占有率已经达到86.4%,如三星、索尼爱立信、小米、OPPO等手机生产厂商都在使用安卓系统。该系统开源免费、执行效率高,其多线程技术开发应用的研究,对提高手机硬件的利用效率,给用户带来良好试用体验,提高手机厂商的企业竞争力有重要作用。 1 多线程介绍 1.1 进程和线程介绍 一般来说,在一定时间内实现多个程序任务执行的程序都会用到“进程”这一概念。进程,即:一个拥有自身独立的内存空间、系统资源的执行程序,其特征为实现内部状态和内部数据的相互独立。线程与进程相似,线程也是一段有一定功能代码组成的流控制。线程的特征为:同类的多个线程可以对内存空间与系统资源进行共享。因此在对资源的占用方面,可以相互切换的线程比进程小很多。一个进程中可以包含诸多线程,此外,主线程对子线程有控制作用,可对子线程启动、停止等动作进行管理。而本文要重点介绍的多线程,指的是单个程序中一起运行的不同线程,不同线程可以执行不一样的任务。其特征是一个程序的多行语句可在某时间同时执行。 1.2 多线程程序消息处理原理 当人们启动一个程序时,系统将建立main线程,主要管理如:activity等应用组件,并对UI相关的事件进行处理,比如用户想要按键或使用屏幕进行绘图,线程会对以上事件进行处理,这是UI线程。安卓的线程模型,所有组件均在main线程中,因此用户在程序中下达下载文件、使用数据库等具有高耗时特征的操作时,就会造成UI线程的运行不畅,并出现程序无法响应的问题。这就要求程序员使用多线程技术,在进行安卓多线程编写时,技术人员应注意以下两点:

Word控件工具箱的使用和实例

Word控件工具箱的使用和实例(多选题制作) 2010-07-27 07:41:48 来源:IT部落窝浏览:4246次 word中的控件工具箱是做什么的,如何使用? Word的控件工具箱在哪里呢?单击菜单“视图——工具栏””项中点“控件工具箱”就可以调出控件工具箱。 Word控件工具箱是做什么的呢?word控件工具箱主要在VBA中窗体内使用,每个控件都是一个工具模块,具体功能通过设置属性和写入VB代码来实现。有些控件也可以在w ord文档中使用。 word控件工具箱其实就是VBA的可视化界面,需要掌握一定的VBA知识,懂一些代码,才能更好的驾驭Word的控件工具箱。 举一个很简单的例子,打开word后,打开控件工具箱,双击命令按钮,页面上就会多出一个Commandbutton,双击“commandbutton”,在end sub上面加这样一行MsgBox 保存退出VBA编辑器,单击一下控件工具箱上第一个钮退出设计模式,现在单击一下页面上的Commandbutton按钮,就会弹出一个提示框 下面我们就利用Word的控件工具箱来制作一道多项选择题。下面是具体操作步骤: 第一步,启动Word,新建一空白文档,输入“计算机的软件系统包括( )”并根据情况设置好字体、字号和颜色。 第二步,依次单击菜单栏中的“视图→工具栏→控件工具箱”命令,在弹出的控件工具箱中选中“复选框”按钮,然后在文档拖拉出一个复选框。 第三步,在拖拉出的复选框(checkbox1)上单击鼠标右键选择属性,打开“属性”对话框,

将其中“名称”修改为“duoxt11”,“Caption”后面的字符修改为题目相应选项字符(如“系统软件”),然后再设置“font”选项设置合适的字体、字号等。其他属性可采用默认值。 第四步,将上面设置好的复选框复制三个,分别将“名称”修改为“duoxt12”、“duoxt13”、“duoxt14”,“Caption”属性分别修改为“应用软件”、“杀毒软件”和“工具软件”。 第五步,选择“控件工具箱”的“命令按钮” ,在文档中添加一个按钮,和上面3中的操作一样将其“名称”修改为“duopd11”,“caption”属性修改为“判断”(也可以修改“font”属性来设置字体、字号等)。然后双击该按钮,进入“VisualBasic编辑器”状态,将下述代码输入到Priv ate Sub Private Sub duopd11_Click()和End Sub代码中间,输入完成后,关闭窗口返回。 If duoxt11.Value = True And duoxt12.Value = True And duoxt13.Value = False And duoxt14.Value = False Then MsgBox "恭喜你,选择正确。", vbOKOnly, "结果" Else If duoxt11.Value = True And duoxt12.Value = False And duoxt13.Value = False And duoxt14.Value = False Or duoxt11.Value = False And duoxt12.Value = True And duoxt13.Value = False And duoxt14.Value = False Then MsgBox "选对了一个,还有一个,再努力一下就胜利了。", vbOKOnly, "提示" Else MsgBox "选择错误!还需要继续努力啊!", vbOKOnly, "提示" End If End If

Android下使用Http协议实现多线程断点续传下载

0.使用多线程下载会提升文件下载的速度,那么多线程下载文件的过程是: (1)首先获得下载文件的长度,然后设置本地文件的长度 HttpURLConnection.getContentLength(); RandomAccessFile file = new RandomAccessFile("QQWubiSetup.exe","rwd"); file.setLength(filesize);//设置本地文件的长度 (2)根据文件长度和线程数计算每条线程下载的数据长度和下载位置。 如:文件的长度为6M,线程数为3,那么,每条线程下载的数据长度为2M,每条线程开始下载的位置如下图所示。 例如10M大小,使用3个线程来下载, 线程下载的数据长度 (10%3 == 0 ? 10/3:10/3+1) ,第1,2个线程下载长度是4M,第三个线程下载长度为2M 下载开始位置:线程id*每条线程下载的数据长度 = ? 下载结束位置:(线程id+1)*每条线程下载的数据长度-1=? (3)使用Http的Range头字段指定每条线程从文件的什么位置开始下载,下载到什么位置为止, 如:指定从文件的2M位置开始下载,下载到位置(4M-1byte)为止 代码如下:HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303"); (4)保存文件,使用RandomAccessFile类指定每条线程从本地文件的什么位置开始写入数据。 RandomAccessFile threadfile = new RandomAccessFile("QQWubiSetup.exe ","rwd"); threadfile.seek(2097152);//从文件的什么位置开始写入数据

非常好用的word实例

綜合練習 1、輸入下麵の文字並按樣文排版,要求如下: (1)標題文字:隸書,一號,桔黃,加空心效果,居中; (2)正文文字:正文所有段落,楷體,四號,加陰影效果。 正文第一段,粉紅,左對齊; 正文第二段中,高明同學為淺黃色,日期、大禮堂為藍色,左對齊; 正文最後兩段,右對齊; (3)段落:正文第二段,首行縮進2個字元。 (4)間距:正文第一段,段前間距1行。 (5)行距:各段行距均為1.5倍行距。 (6)邊框:為標題加段落邊框,上下邊框線為雙線型,桔黃色,0.5磅;左右邊框線為虛線型,1.5磅,桔黃色。(6)底紋:為所有文字加底紋,淺綠色。 (7)橫線:為正文上下加橫線效果,如樣文所示。2、輸入“(Internet)連接了全球不計其數の網路與電腦”,利用“格式刷”功能把它の格式設置為與“大禮堂”相同の格式。 3、輸入幾段落為其加編號。起始編號為3,編號樣式為: 4、排版如下內容: (1)X2+Y2=Z2 (2)中 zhōng 南 nán 海 hǎi 綜合練習:輸入如下文字,按要求進行排版。 樣文如下: 1、要求如下: 1)標題為藝術字,藝術字式樣為“第二行第三列”の式樣,藝術字形狀為“兩端遠”。 2)正文第一段:華文行楷,三號,綠色,首行縮進2個字元。 3)正文第二段:隸書,三號,金色,首行縮進2個字元。

4)在第一段中加入豎排文本框,雙線型,4.5磅,桔黃色,文本框內文字為華文行楷,三號,紅色; 5)文本框環繞方式:四周環繞。 6)分欄:正文第二段分兩欄,加分隔線。 7)在文檔中插入一幅圖片,顏色為沖蝕,環繞方式為襯於文字下方,並為該圖片加邊框,線型為雙線型,6磅, 紅色。 綜合練習: 1、繪製如下表格 2、插入如下表格(表格在下一頁),要求如下: 1)表格第一行行高為2.5釐米,其他行行高為1.5釐米,表格中每一列列寬為3釐米。 2)表格內第一行文字,華文隸書,三號,中部居中; 3)表格最後一行文字,黑體,加粗,四號,靠下居中; 4)表格中其他文字,華文隸書,小四號,中部居中; 5)表格線,外邊框線為“雙線型”,3磅,綠色;內邊框線為“虛線型”,1.5彩磅,綠色; 6)底紋,為表格加如圖所示底紋。 3、繪製如下表格:

AndroidUI之线程与进度对话框

//创建一个进度条对话框 final ProgressDialog progressdialog=new ProgressDialog(MainActivity.this); progressdialog.setTitle("测试"); progressdialog.setMessage("正在对话框与线程"); progressdialog.show();//显示对话框 //创建线程 new Thread(){ public void run(){ try{ sleep(1000);//时间间隔1秒 }catch(Exception e){ e.printStackTrace(); }finally{ progressdialog.dismiss();//卸载对话框对象 } } }.start(); 菜单的创建于事件监听 public boolean onCreateOptionsMenu(Menu menu) { int a=Menu.NONE;//声明菜单顺序ID int b=Menu.NONE+1; int c=Menu.NONE+2; int d=Menu.NONE+3; menu.add(0, 1, a, "a");//第一个参数:分组,第二个参数:菜单的Id, 第三个参数:菜单的顺序,第四个参数:显示菜单的文字 menu.add(1, 2, b, "b"); menu.add(2, 3, b, "c"); menu.add(2, 4, d, "d"); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case 1: Toast.makeText(MainActivity.this,"a", 1).show(); break; case 2: Toast.makeText(MainActivity.this,"b", 1).show(); break;

带你深入解读:基于多线程技术的PLC与PC的通讯方式

带你深入解读:基于多线程技术的PLC与PC的通讯方式 0.引言在现代工业控制系统中,PLC以其高可靠性、适应工业过程现场、强大的联网功能等特点,被广泛应用。可实现顺序控制、PID回路调节、高速数据采集分析、计算机上位管理,是实现机电一体化的重要手段和发展方向。但PLC无法单独构成完整的控制系统,无法进行复杂的运算和显示各种实时控制图表和曲线,无良好的用户界面,不便于监控。将个人计算机(PC)与PLC结合起来使用,可以使二者优势互补,充分利用个人计算机强大的人机接口功能、丰富的应用软件和低廉的价格优势,组成高性能价格比的控制系统。 1.系统构成推进系统中,PC机选用工控计算机。它是整个控制系统的核心,是上位机。其主要利用良好的图形用户界面,显示从PLC接收的开关量和控制手柄的位置,进行一些较复杂的数据运算,并且向PLC发出控制指令。 PLC是该系统的下位机,负责现场高速数据采集(控制手柄的位置),实现逻辑、定时、计数、PID调节等功能,通过串行通讯口向PC机传送PLC工作状态及有关数据,同时从PC机接受指令,向蜂鸣器、指示灯、滑油泵、控制手柄的位置等发出命令,实现PC机对控制系统的管理,提高了PLC的控制能力和控制范围,使整个系统成为集散控制系统。 2.通讯协议计算机与PLC之间的通信是建立在以RS232标准为基础的异步双向通信上的,FX系列PLC有其特定的通信格式,整个通信系统采用上位机主动的通信方式,PLC内部不需要编写专门的通信程序,只要把数据存放在相应的数据寄存器中即可,每个数据寄存器都有相应的物理通信地址,通信时计算机直接对物理通信地址进行操作。通信过程中,传输字符和命令字以ASCⅡ码为准,常用的字符及其ASCⅡ码对应关系。 计算机与PLC进行通讯时,计算机与PLC之间是以帧为单位进行信息交换的,其中控制字符ENQ、ACK、NAK,可以构成单字符帧发送和接受,其余的信息帧发送和接受时都是由字符STX、命令字、数据、字符ETX以及和校验5部分组成。

Word操作技巧大全

Word操作技巧大全(转) Word操作技巧大全(64页) 摘录者 https://www.360docs.net/doc/bf18848676.html,/shufayan Word中的―选中‖方法知多少? 一、常见的―选中‖方法: ü全选(快捷键Ctrl+A):就是全部选中文档内的所有内容。这所有内容包括:文字、表格、图形、图像等可见的和不可见的标记。 ü按住Shift+Page Down从光标处向下选中一屏,Shift+Page Up从光标处向上选中一屏。ü按住Shift+左选中光标左边第一个字符,Shift+右选中光标右边第一个字符,Shift+上选中从光标处向上到同列的字符,Shift+右选中从光标处向下到同列的字符。(注:此处的?、à、á、a表示:上、下、左、右光标键) üCtrl+Shift+á从光标向上选中光标所在的一段,Ctrl+Shift+a从光标向下选中光标所在的一段。 ü扩展选中(快捷键F8):按一次打开扩展功能;再按一次选中光标所在的位置的单词(若是中文的话选中光标所在的后一个字);再按一次选中光标位置所在的一句;再按一次选中光标位置所在的一段落;再按一次则相当于全选。取消扩展功能,按Esc键。 ü把光标放到页面的左边,出现形如―?‖,点击就选中一行。上下拖到就选中若干行。 ü Alt+鼠标拖动:选中矩形区域。 ü按住Shift选中多个对象(非嵌入型的),也可以用绘图工具栏第二个按钮的―选择对象‖(―?‖)来选择。 对于Word2003可以把光标放到任一位置,点右键/―选择格式相似的文本‖功能,这个功能有点类似于F4,比较―另类‖。―相似‖两字很值得玩味,多试几遍,其意自现。 二、―不常用‖的―选中‖方法 说他―不常用‖并不真的是不常用,而是对于新手而言的不常用。 ü通过菜单格式/样式和格式(Word2003版,别的版本类似),点击要选择的样式,点右边的下拉框,出现―选择所有XX实例‖、―修改‖、―删除‖等。此处的XX代表使用这种样式的段落是多少,点选―选择所有XX实例‖就选中了所有应用了此样式的段落。(注:对于规范的排版,这个是最好用的,可能我们只要四五个样式就把一篇小册子搞定。) ü不得不提到的替换(快捷键Ctrl+F):在查找内容中输入要选中的文字、或点格式、高级等设置,再点选突出显示所有在该范围找到的项目,在选查找全部、关闭,这就就选中了你可以想到的任意的内容。这个看似简单,其实是所有技巧中伸缩度最大的一个。 三、一种类似于图形软件方面的选中技巧: 在Photoshop中,有一个命令叫―保存选区‖非常好用,在Word中也可以找到类似的方法,这就是书签。利用书签―存储选区‖也是很好的方法,但要注意,他必须是连续的区域。 方法是:先选中要定义的区域,插入/书签,输入书签名,定义一个书签。使用时:插入/书签,找到想选中的书签,点―定位‖就选中了书签所代表的选择。这个功能在后台VBA中也很好用,制定特定模板的首选。 特例:对段落进行样式时,不需要选中段落的,只要把光标放到段落中就行了。

(完整版)cadence PCB 画图(傻瓜教程快速入门)

cadence 画 PCB 板傻瓜教程(转帖) 复制于某网站,谢谢。拿出来分享吧,希望对初学者能有帮助,可以很快了解 Cadence 的使用,谢谢共享者。 一.原理图 1.建立工程 与其他绘图软件一样,OrCAD 以Project 来管理各种设计文件。点击开始菜单,然后依次是所有程序—打开 cadence 软件—》一般选用 Design Entry CIS,点 击Ok 进入Capture CIS。接下来是 File--New--Project,在弹出的对话框中填入工程名、路径等等,点击 Ok 进入设计界面。 2.绘制原理图 新建工程后打开的是默认的原理图文件 SCHEMATIC1 PAGE1,右侧有工具栏,用 于放置元件、画线和添加网络等等,用法和 Protel 类似。点击上侧工具栏的Project manager(文件夹树图标)或者是在操作界面的右边都能看到进入工程管 理界面,在这里可以修改原理图文件名、设置原理图纸张大小和添加原理图库 等等。 1)修改原理图纸张大小: 双击 SCHEMATIC1 文件夹,右键点击 PAGE1,选择 Schematic1 Page Properties,在 Page Size 中可以选择单位、大小等; 2) 添加原理图库: File--New--Library,可以看到在 Library 文件夹中多了一个 library1.olb 的原理图库文件,右键单击该文件,选择 Save,改名存盘;(注意:在自己话原 理图库或者封装库的时候,在添加引脚的时候,最好是画之前设定好栅格等参数,要不然很可能出现你画的封装,很可能在原理图里面布线的时候通不过, 没法对齐,连不上线!) 3)添加新元件: 常用的元件用自带的(比如说电阻、电容的),很多时候都要自己做元件,或 者用别人做好的元件。右键单击刚才新建的 olb 库文件,选 New Part,或是New Part From Spreadsheet,后者以表格的方式建立新元件,对于画管脚特多的芯片元件非常合适,可以直接从芯片 Datasheet 中的引脚描述表格中直接拷贝、粘贴即可(pdf 格式的 Datasheet 按住Alt 键可以按列选择),可以批量添加管脚,方便快捷。 4)生成网络表(Net List): 在画板 PCB 的时候需要导入网络表,在这之前原理图应该差不多完工了,剩下 的工作就是查缺补漏。可以为元件自动编号,在工程管理界面下选中.dsn 文件,然后选 To ol s--A n n o t a te,在弹出的对话框中选定一些编号规则,根据需求进行修改 或用默认设置即可。进行 DRC 检测也是在生成网络表之前的一项重

基于linux的socket多线程通信

1、网络中进程之间如何通信? 本地的进程间通信(IPC)有很多种方式,但可以总结为下面4类: ?消息传递(管道、FIFO、消息队列) ?同步(互斥量、条件变量、读写锁、文件和写记录 锁、信号量) ?共享内存(匿名的和具名的) ?远程过程调用(Solaris门和Sun RPC) 但这些都不是本文的主题!我们要讨论的是网络中进程之间如何通信?首要解决的问题是如何唯一标识一个进程,否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。其实TCP/IP协议族已经帮我们解决了这个问题,网络层的―ip地址‖可以唯一标识网络中的主机,而传输层的―协议+端口‖可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。 使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说―一切皆socket‖。 2、什么是Socket? 上面我们已经知道网络中的进程是通过socket来通信的,那什么是socket呢?socket起源于Unix,而Unix/Linux基本哲学之一就是―一切皆文件‖,都可以用―打开open –> 读写write/read –> 关闭close‖模式来操作。我的理解就是Socket就是该模式的一个实现,socket 即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭),这些函数我们在后面进行介绍。 socket一词的起源 在组网领域的首次使用是在1970年2月12日发布的文献IETF RFC33中发现的,撰写者为Stephen Carr、Steve Crocker和Vint Cerf。根据美国计算机历史博物馆的记载,Croker写道:―命名空间的元素都可称为套接字接口。一个套接字接口构成一个连接的一端,而一个连接可完全由一对套接字接口规定。‖计算机历史博物馆补充道:―这比BSD的套接字接口定义早了大约12年。‖ 3、socket的基本操作 既然socket是―open—write/read—close‖模式的一种实现,那么socket就提供了这些操作对应的函数接口。下面以TCP为例,介绍几个基本的socket接口函数。 3.1、socket()函数 int socket(int domain, int type, int protocol); socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。 正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket 函数的三个参数分别为:

Android应用程序开发完整训练:从零起步通过23个动手实战案例精通App开发

从零起步,24小时内通过23个动手实战案例,循序渐进的对Android商业级别的应用程序开发要点各个击破,依托于在多年的Android(6款完整的硬件产品和超过20款应用软件)开发和企业级培训经验(超过150期的次Android的企业内训和公开课),旨在在实务的基础之上帮助你完成任何复杂程序的高质量Android应用程序开发,让Android开发跟上想象的速度。最后,通过ActivityManagerService揭秘Android应用程序一切行为背后的核心根源,让你从此开发应用程序居高零下、举重若轻。 课程要点: 1,抽取Android应用开发中用到的最精华的Java技术加以剖析; 2,从零起步构建Android开发环境和编写并彻底剖析第一个Android程序; 3,彻底剖析不同Activity之间所有的交互模式; 4,根据商业化场景彻底剖析Android的生命周期及其使用的最佳时间; 5,使用JUnit测试Android业务代码; 6,掌握Android基本和核心的UI开发技术; 7,”Android商业化高级UI实战”是根据过去20多款商业级别Android应用程序开发尤其是类似CRM系统中最经典、最经常使用的技术抽取而成,掌握之后基本上不会在遇到UI 方面的难点; 8,细致剖析并实战Android性能测试,找出性能瓶颈,并进行代码优化,分享代码优化的最佳实践; 9,对数据的处理时Android绝大多数应用程序的核心,尤其是对CRM系统而言,这一天,我们会对Android中的本地数据处理方式及其商业使用场景进行彻底剖析和实战; 10,从SharedPreferences到内部文件系统,从SDCard操作到SQLite数据库,从XML 和JSON的解析于生成到数据共享统一接口ContentProvider,对Android本地的数据处理方式进行地毯式轰炸; 11,通讯录的操作的原理、流程和场景等进行了情景再现性的代码实战; 12,通过Android手机卫士商业级别的代码案例实战Android中BroadcastReceiver和Service; 13,根据过去20多款程序的商业实战总结出了能够解决基于HTTP协议的任意文件类型、任意大小文件的网络上传和下载,Android网络开发从此一劳永逸; 14,实战WiFi数据交换; 15,尤其是额外提到异步http框架,具备很强的商业价值; 16,Android横竖屏切换的经典场景、生命周期和解决方案; 17,实战构建多语言国际化的Android应用程序; 18,如何编译APK来提高应用的安全性; 19,如何反编译Android应用 20,通过Android中WebView的特性洞悉Android中JavaScript与Java相互沟通的密码,追寻浏览器和HTML5开发的架构和技术实现根源; 21,使用NDK等技术利用C/C++的高效性来提高应用程序的性能; 22,实现Android中以Looper、Handler、Message、MessageQueue为核心的线程间通信方式; 23,实战并剖析AsyncTask框架实现的源代码,并提出对AsyncTask缺陷的解决方案;

傻瓜式开展培训规划工作完整版

傻瓜式开展培训规划工 作 HEN system office room 【HEN16H-HENS2AHENS8Q8-HENH1688】

傻瓜式开展年度培训规划工作 又到了一年一度的开展年度培训规划工作的时刻了。人力资源部作为公司的培训管理组织者,又需要耗费脑汁去琢磨怎么做好这项工作。这里面,有培训体系完善的企业,也有没有任何培训体系根基的企业;有十分专业的HR,也有菜鸟级的新手;有富的流油的预算支持的培训费用,也有捉襟见肘无米下锅的培训费用…… 不同条件可以采用不同的培训规划方案,但是笔者根据多年的培训规划经验简单的分享一种傻瓜式的开展年度培训规划工作的方法步骤,有兴趣的朋友可以借鉴来做。 首先弄清楚几个基本的定义与要求: 1、年度培训规划必须依据公司拨付给你的年度培训预算来进行组织开展,在预 算范围内合理开展各项培训;同时也可以在年度培训规划中简单分析明年培 训费用使用的情况、结构比例,比如:新员工培训费用占了多少、营销人员 的培训费用占了多少,管理者的培训费用占了多少等; 2、年度培训规划的开展必须有两个必不可少的步骤:首先开展年度培训需求分 析,形成年度培训需求分析报告;然后据此制定年度培训计划; 3、年度培训计划最后必须形成表格,表格栏目的要素必须包括:部门名称、课 程名称、课程主要内容、对应的课程课时、对应课程讲师姓名、对应课程费 用、培训课程开展拟安排的时间、参训人数; 4、年度培训计划的课程时间原则上不建议确定具体的日期,因为在今年年底确 定明年课程的具体时间毫无意义,中间不可控因素太多,但必须确定到每月 的上中下旬,同时注意错开节假日、公司固定的会议和活动时间; 5、年度培训计划必须填写每门课程的参训人数,这个数据是预测、分析公司、 各部门的人均课时数的关键,一个学习型组织,它一年的人均课时不能低于 40小时,低于40个小时说明培训力度太小,大家没有机会学习,高于300 个小时说面培训力度太大,会影响工作。

word2010教案全

1.1.1文档的操作 (1)新建文档 新建空白文档 根据模板创建文档 【案例1-1】创建书法字帖,提高书法造诣

【案例1-2】创建奖状模板 (1) 设置纸张大小为宽30厘米,高20厘米; 图2-1 【页面设置】对话框(2) 设置图中所示的页面边框;

图2-2 页面边框的设置 (3) 按照图示输入内容; (4) 将该模板保存为“奖状”。 图2-3 模板的保存 (2)打开文档 (3)保存文档 【重点】“另存为”对话框 【重点】自动保存功能设置 (4)打印文档 打印预览与打印设置 【技巧】办公室打印实用技巧 1、逆序打印 在日常办公中,我们打印后经常在装订之前还需要重新排序,因为一般打印时候第一张会在最底下。 逆序打印功能可以帮助你解决这个问题。 【Word选项】→【高级】→【逆序打印页面】选项

图2-4 “逆序打印”设置 【提示】逆序打印之后影响实际打印输入的效果,而打印预览仍然是以正常顺序显示预览效果。 2、文件内容先知晓 3、预览效果还省墨水 【Word选项】→【高级】→【使用草稿品质】 (5)窗口操作 同时编辑一个文档的多个部分 快速显示多个文档 快速切换多个文档

第2章Word 2010基础入门与操作(1学时) 【主要讲授内容及时间分配】 2.1 Word 2010操作界面简介(5分钟) 2.2 文本的操作(10分钟) 2.3插入符号和日期(10分钟) 2.4项目符号和编号功能(10分钟) 2.5文档的视图方式(5分钟) 2.6拼写和语法检查(5分钟) 【重点与难点】 1、重点: 文本操作的方式、符号和日期的使用、项目符号和编号的使用、视图方式的区别。 2、难点: 【教学要求】 【实施方法】 课堂讲授,PPT配合 第2章Word 2010基础入门与操作 2.1文本操作 2.1.1选定文本 (1)使用鼠标选择文本 ●Ctrl+A,可以选择当前文档的全部内容 ●【双击鼠标】选择短句; ●【3击鼠标】选择一段文本 ●【选定栏的使用】 ?单击→行 ?单击+拖动→多行 ?双击→段落 ?双击+拖动→多个段落 ?三击→整篇文档 ●Alt+拖动→选定竖块文本 2.1.2选择文本与选择段落 选择了段落中的文字移动只移动其中的文字,而选择了整个段落移动后,不仅移动其文字,而且也移动了文字的格式和段落的换行符。 2.1.3复制与粘贴 1、复制 2、粘贴

相关文档
最新文档