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_INFORMA TION访问权限。对于线程的优先权层次的设置,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;

V oid CtestView::OninitialUpdate()

{

hWnd=GetSafeHwnd();

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

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

Cview::OnInitialUpdate();

}

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

V oid 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;

}

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

V oid CtestView::OninitialUpdate()

{

hWnd=GetSafeHwnd();

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

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

pThread->m_bAutoDelete=FALSE;

Cview::OnInitialUpdate();

}

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

V oid 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_A TTRIBUTES lpThreadAttributes, // 安全属性结构指针,可为NULL;DWORD dwStackSize, // 线程栈大小,若为0表示使用默认值;

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

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

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

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

);

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

HANDLE CreateEvent(

LPSECURITY_A TTRIBUTES lpEventAttributes, // 安全属性结构指针,可为NULL;BOOL bManualReset, // 手动清除信号标记,TRUE在WaitForSingleObject后必须手动//调

用RetEvent清除信号。若为FALSE则在WaitForSingleObject

//后,系统自动清除事件信号;

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

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

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

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

);

HANDLE CreateMutex(

LPSECURITY_A TTRIBUTES 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;

}

/*快速排序思想:快速排序是分治思想的一种应用,它先选取一个支点,然后把小于支点的元素交换到支点的前边,把大于支点的元素交换到支点的右边。然后再对支点左边部分和右边部分进行同样的处理,这样若干次之后,数据就会变得有序。下面的实现使用了递归

建立两个游标:iLow,iHigh;iLow指向序列的第一个元素,iHigh指向最后一个先选第一个元素作为支点,并把它的值存贮在一个辅助变量里。那么第一个位置就变为空并可以放置其他的元素。这样从iHigh指向的元素开始向前移动游标,iHigh查找比支点小的元素,如果找到,则把它放置到空置了的位置(现在是第一个位置),然后iHigh游标停止移动,这

时iHigh指向的位置被空置,然后移动iLow游标寻找比支点大的元素放置到iHigh指向的空置的位置,如此往复直到iLow与iHigh相等。最后使用递归对左右两部分进行同样处理*/

int QuickSort(long* Array, int iLow, int iHigh)

{

if(iLow >= iHigh) return 1; //递归结束条件

long pivot = Array[iLow];

int iLowSaved = iLow, iHighSaved = iHigh; //保未改变的iLow,iHigh值保存起来

while (iLow < iHigh)

{

while (Array[iHigh] >= pivot && iHigh > iLow) //寻找比支点大的元素

iHigh -- ;

Array[iLow] = Array[iHigh]; //把找到的元素放置到空置的位置

while (Array[iLow] < pivot && iLow < iHigh) //寻找比支点小的元素

iLow ++ ;

Array[iHigh] = Array[iLow]; //把找到的元素放置到空置的位置

}

Array[iLow] = pivot; //把支点值放置到支点位置,这时支点位置是空置的

//对左右部分分别进行递归处理

QuickSort(Array, iLowSaved, iHigh-1);

QuickSort(Array, iLow+1, iHighSaved);

return 0;

}

//每一个线程都要使用这个函数进行输出,而且只有一个显示器,产生多个线程

//竞争对控制台的使用权。

void PrintResult(long* Array, int iLength, const char* HeadStr)

{

WaitForSingleObject(evtPrint, INFINITE); //等待事件有信号

//EnterCriticalSection(&csPrint); //标记有线程进入临界区

//WaitForSingleObject(mtxPrint, INFINITE); //等待互斥量空置(没有线程拥有它)

int i;

printf("%s: ", HeadStr);

for (i=0; i

{

printf("%d,", Array[i]);

Sleep(100); //延时(可以去掉)

/*只是使得多线程对临界区访问的问题比较容易看得到

如果你把临界控制的语句注释掉,输出就会变得很凌乱,各个排序的结果会

分插间隔着输出,如果不延时就不容易看到这种不对临界区控制的结果

*/

}

printf("%d\n", Array[i]);

SetEvent(evtPrint); //把事件信号量恢复,变为有信号

}

四、小结

对复杂的应用程序来说,线程的应用给应用程序提供了高效、快速、安全的数据处理能力。本实例讲述了线程处理中经常遇到的问题,希望对读者朋友有一定的帮助,起到抛砖引玉的作用

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

多线程技术在数据通信中的应用 发表时间: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)

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; } }

带你深入解读:基于多线程技术的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部分组成。

基于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 函数的三个参数分别为:

线程间通信方式

线程间通信方式收藏 线程间通信可以通过下列三种方法: 1)使用全局变量实现线程间通信 2)使用消息实现线程间通信 3)使用CEvent类实现线程间通信 使用全局变量实现线程间通信: 定义一个全局变量,不同的线程间可以通过修改全局变量的值来进行通信。例如:定义一个控制线程的全局变量 volatile int threadController; 当threadController值为1时,线程running;当threadController为0时,线程stop,所以可以通过修改threadController的值来控制线程的运行。 使用消息实现线程间通信: 步骤: 1)在View.h中定义消息: 例如: const WM_THREAD_SENDMESS=WM_USER+20; 2)在view.h中添加消息函数声明: 例如: afx_msg int OnThreadSendmess(); 3)在view.cpp中添加消息映射: ON_MESSAGE(WM_THREAD_SENDMESS,OnThreadSendmess) 4)在view.cpp中添加OnThreadSendmess()的实现函数; 5)在线程函数中添加PostMessage消息Post函数; UINT TreadProc(LPVOID param) { CThreadTestApp *pApp=(CThreadTestApp *) AfxGetApp(); CMainFrame *pMainFrame = (CMainFrame *)pApp->GetMainWnd(); CThreadTestView *pView = (CThreadTestView *) pMainFrame->GetActiveView(); pView->m_strMessage = “启动了一个线程” ; while(threadController) { ::Sleep(1000); ::PostMessage((HWND)param, WM_THREAD_SENDMESS, 0, 0); } pView->m_iTime =0;

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 ) 启动该用户界面线程,其中第一个参

多线程通讯-TCP聊天程序-VC++

操作系统课程设计——多线程通讯-TCP聊天程序 -VC++ 操作系统课程设计任务书 一、设计题目:多机进程通信 应用Socket进程通信技术编写聊天室程序,实现基于服务器的并发多机信息转发。如果各客户端在线则可以实时聊天、发送接收文件,如果某客户端离线,则发送给他的消息可由服务器端进行内容缓存,待重新连线后可以自动接收通过服务器转发的信息或文件。缓存与转发的控制算法可参考操作系统课程中生产者消费者进程同步方法、缓冲池技术等相关理论。 二、设计思路和功能的详细描述 采用TCP协议,所以属于客户机/服务器模式,因此需要聊天服务器端和聊天客户端两个程序,实现的功能是:任意一台装有客户端程序的计算机都可以通过服务器端的IP地址与服务器相连,然后进入聊天室与连接到服务器的其他客户进行聊天。当客户聊天结束时,可以点断开与服务器断开连接,以释放进程让其他等待的客户进入聊天室,本聊天室最大同时支持50个客户端的连接,如果服务器配置较高可以修改程序来增加同时连接数。 三、采用的方法、技术、运行环境及其配置 本聊天程序采用TCP协议,用VC++编写,属于客户机/服务器模式。采用了多线程的机制。其中使用windows Sockets实现多台计算机(多个进程)间的通信,SOCKET实际在计算机中提供了一个通信端口,可以通过这个端口与任何一个具有SOCKET接口的计算机通信。应用程序在网络上传输,接收的信息都通过这个SOCKET接口来实现。在客户机/服务器模式中客户应用程序向服务器程序请求服务。一个服务程序通常在一个众所周知的地址监听对服务的请求,也就是说,服务进程一直处于休眠状态,直到一个客户对这个服务的地址提出了连接请求。在这个时刻,服务程序被“惊醒”并且为客户提供服务即对客户的请求作出适当的反应。本聊天程序就是基于这中思想实现的,程序分为两大部分:TCP聊天服务器端和TCP 聊天客户端。两者都拥有各自的SOCKET接口,其中服务器端SOCKET接口需要绑定到固定地址上(实现语句:ock=Socket(AF_INET,SOCK_STREAM,0);),等待客户端的连接(实现语句:listen(sock,5);)。等待客户端的连接的过程就是通过多进程机制来实现的。 聊天程序是在VISUAL C++6.0上编译实现的,在WINDOWS2000,XP上测试运行成功。 对客户计算机配置无特殊要求,由于所设置的最大连接进程为50,所以对服务器要求也不高。

linux多线程通信课程设计报告

课程设计说明书(论文) 题目线程同步服务器编程 课程名称通信应用软件课程设计 专业 班级 学生姓名 学号 设计地点 指导教师 设计起止时间 目录 1.问题描述 (3)

1.1背景 (3) 1.2题目要求 (3) 1.3源代码运行方法及功能描述: (3) 1.4关系模式 (4) 2.方案图表设计 (4) 2.1TCP实验原理图形 (4) 2.2互斥锁实验原理图形 (5) 3.部分函数源代码 (6) 3.1互斥锁 (6) 3.2TCP服务器 (8) 3.3倒序输出信息 (8) 4.记过数据处理 (9) 5.结束语 (11) 6.参考文献 (11)

1.问题描述 1.1背景 进程是一个可拥有资源的基本单位,也是一个可独立调度和分配的基本单位。一个进程至少有一个线程。 在多进程/多线程环境中,线程及进程同步是十分重要的。该技术解决了由并发而产生的同步问题,使得并发服务器能可靠的运行。 和进程相比,线程的最大优点之一是数据的共享性,各个进程共享父进程处继承的数据段,可以方便的获得、修改数据。但这也给多线程编程带来了许多问题。必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的,即同时不能运行一个函数的多个拷贝(除非使用不同的数据段)。在函数中声明的静态变量常常带来问题,函数的返回值也会有问题。因为如果返回的是函数内部静态声明的空间的地址,则在一个线程调用该函数得到地址后使用该地址指向的数据时,别的线程可能调用此函数并修改了这一段数据。在进程中共享的变量必须用关键字volatile来定义,这是为了防止编译器在优化时(如gcc中使用-OX参数)改变它们的使用方式。为了保护变量,必须使用信号量、互斥等方法来保证对变量的正确使用。 1.2 题目要求: 1.在了解线程同步、进程同步及进程间通信技术的基础上,把udpclient改成tcpclient,即把UDP改成TCP连接,其余功能要求不变。 2.在客户端同时显示服务器的时间。 3. 在服务器端倒序显示客户端的信息 1.3 功能简述: 源程序运行方法: 先运行tcpserver.c,然后打开一个终端启动服务器程序stt_client.c,再打开一个新的终端运行客户程序(stt_client.c)3次。 tcpserver.c程序功能:

现场总线CAN通信中多线程的应用

第27卷 第2期2004年6月 东 华 理 工 学 院 学 报 JOURNA L OF E AST CHI NA I NSTIT UTE OF TECH NO LOGY V ol 127 N o 12 Jun.2004 收稿日期:2003209210 作者简介:郑勇芸(1979— ),女,助教,研究方向为数据库应用及软件工程。现场总线CAN 通信中多线程的应用 郑勇芸1,2 饶运涛1 邹继军1 朱兆优1 (1.东华理工学院电子与自动化系,江西抚州 344000;2.上海师范大学数理信息学院,上海 200234) 摘 要:现场总线是用于自动化系统最底层的现场设备或仪表互联的通信网络。CAN 是现场总线中的一种,具有较高的性能价格比和较强的实时处理能力。文章以采用CAN 总线的通讯网络结构的校园消费系统为例,介绍在CAN 总线通信中如何采用基于多线程的通信程序实现实时的接收、处理和发送数据的功能,对多个线程之间的协作关系进行了分析,以及对采用信号量实现线程的互斥和同步问题进行了详细的论述,最后给出了利用Delphi 编写的线程的框架。关键词:CAN ;多线程;Delphi5;现场总线 中图分类号:TP311.5 文献标识码:A 文章编号:1000-2251(2004)02-196-05 现场总线(FieldBus )是智能现场设备和自动化系统的数字式、串行双向传输、多分支结构的通信 网络,是计算机网络在自控领域的衍生。C AN (C on 2trollers Area Netw ork )是现场总线中的一种,它是BOSCH 公司为分布式系统在强电磁干扰等环境下能够可靠工作而开发的。因其具有较高的性能价格比和较强的实时处理能力,而被更多的现场控制系统采用。本文涉及的是作者在一校园消费系统中成功利用C AN 作为连接所有消费点与后台服务器的通信网络,实现了实时、灵活、高可靠性的运行。在此系统中,所有节点都是对等的,可以随时发送报文,因此在服务器端主要任务是及时处理来自各消费点的信息并给予回复。下面介绍的是如何利用多线程技术提高系统的工作效率。 1 系统的组成 图1所示的是校园消费系统的总体结构图。 每台窗口机和服务器都是一个C AN 节点,窗口机方是单片机与C AN 控制器组成,服务器上有一块C AN 通信卡,在C AN 网控器的组合下它们通过双绞线相互连接(饶运涛等,2003)。 系统中主要的通信任务是窗口机与服务器之间的信息交换。消费者在窗口机上进行刷卡消费的整个过程可分为以下几个步骤: (1)用户刷卡,窗口机把卡号发送给服务器。(2)服务器根据卡号查找数据库, 把用户余额 图1 校园消费系统总体结构 Fig.1 The whole structure of the consumption system of compus 或其它信息发往相应的窗口机。 (3)窗口机显示用户余额。 (4)操作员扣除消费额后,再把消费额发给服务器。 (5)服务器更新数据库中用户金额并给窗口机应答。 由于用户卡内并不记录余额,必须通过窗口机向服务器请求数据,而且记录消费信息及更新用户余额的工作也在服务器端进行。所有的数据需利用C AN 总线进行传输。 2 系统的工作原理 整个系统的通信采用C AN 总线组建的多分支的网络结构。系统服务器端的C AN 通信卡作为计算机和C AN 总线网络的联接设备,负责将网络中

C#多线程通信之委托(事件)

C#多线程通信之委托(事件) 在研究C# 线程之间通信时,发现传统的方法大概有三种 ①全局变量,由于同一进程下的多个进程之间共享数据空间,所以使用全局变量是最简单的方法,但要记住使用volatile进行限制。 ②线程之间发送消息(这个随后文章中会讨论到)。 ③CEvent为MFC中的一个对象,可以通过对CEvent的触发状态进行改变,从而实现线程间的通信和同步,这个主要是实现线程直接同步的一种方法。 本文介绍的一种方法是这三种之外的一种方法,本文中实例是通过创建一个线程类,通过委托事件把值传送到Form所在的类中,同时更新Form类中的一个控件(Label)中的值。 实现功能比较简单,目的是实现此功能,先把代码贴上: MyThread.cs https://www.360docs.net/doc/6f2167638.html,ing System; https://www.360docs.net/doc/6f2167638.html,ing System.Threading; 3. https://www.360docs.net/doc/6f2167638.html,space ThreadsComm 5.{ 6. public delegate void ReadParamEventHandler(string sParam); 7. class MyThread 8. { 9. public Thread thread1; 10. private static ReadParamEventHandler OnReadParamEvent; 11. public MyThread() 12. { 13. thread1 = new Thread(new ThreadStart(MyRead)); 14. thread1.IsBackground = true; 15. thread1.Start(); 16. } 17. public event ReadParamEventHandler ReadParam 18. { 19. add { OnReadParamEvent += new ReadParamEventHandler(value);} 20. remove{ OnReadParamEvent -= new ReadParamEventHandler(value);} 21. } 22. protected void MyRead() 23. { 24. int i = 0; 25. while (true) 26. { 27. Thread.Sleep(1000); 28. i = i + 1; 29. OnReadParamEvent(i.ToString());//触发事件30. } 31. } 32. } 33.}

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

VC中利用多线程技术实现线程之间的通信 当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的大型应用软件无一不是多线程多任务处理,单线程的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本实例针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,分别进行探讨,并利用多线程技术进行线程之间的通信,实现了数字的简单排序。 一、实现方法 1、理解线程 要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应于Visual C++中的CwinThread类对象。单独一个执行程序运行时,缺省地包含的一个主线程,主线程以函数地址的形式出现,提供程序的启动点,如main()或WinMain ()函数等。当主线程终止时,进程也随之终止。根据实际需要,应用程序可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。 一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的CPU时间片,在某一个时刻,CPU只执行一个时间片内的线程,多个时间片中的相应线程在CPU内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排CPU的时间,优先级高的线程优先运行,优先级低的线程则继续等待。 线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程CWinAPP对象就是一个用

Win32多线程编程 — 线程同步与通信

一.线程间数据通信 系统从进程的地址空间中分配内存给线程栈使用。新线程与创建它的线程在相同的进程 上下文中运行。因此,新线程可以访问进程内核对象的所有句柄、进程中的所有内存以及同 一个进程中其他所有线程的栈。这样一来,同一个进程中的多个线程可以很容易的相互通信。 到目前为止,将数据从一个线程传到另一个线程的惟一方法是在创建线程时传递给新线 程一个指针参数(LPVOID lpParam)。参数lpParam为LPVOID指针类型,我们可在其中 存储普通的数值(size为平台地址总线宽度),也可以存放指向某个数据结构(struct 或class)的地址。在新线程函数中,解引用时需要强制类型转换回原类型,以进行正确 的访问。 以下代码段演示了一个典型的多线程场景。 // A typical multithread scene DWORD WINAPI FirstThread(PVOID lpParam) { // Initialize a stack-based variable int x = 0; DWORD dwThreadID; // Create a new thread. HANDLE hThread = CreateThread(NULL, 0, SecondThread, (LPVOID)&x, 0, &dwThreadID); // We don't reference the new thread anymore, // so close our handle to it. CloseHandle(hThread); // Our thread is done. // BUG:our stack will be destroyed, // but SecondThread might try to access it. return 0; } DWORD WINAPI SecondThread(LPVOID lpParam) { // Do some lengthy processing here. // ... // Attempt to access the variable on FirstThread's stack. // NOTE:This may cause an access violation - it depends on timing! *((int*)lpParam) = 5; // ... return 0; } 上述场景中,Windows没有维持线程之间的“父子关系“,即父线程FirstThread 已经终止运行,而子线程SecondThread仍在继续运行。以上父子关系只是为了一种解说 上的方便,实际上FirstThread和SecondThread具有相同的优先级(默认是 normal),

线程通信的经典问题 生产者和消费者

/** * 线程通信的应用:经典例题:生产者/消费者问题 * * 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品, * 店员一次只能持有固定数量的产品(比如:20),如果生产者试图生产更多的产品,店员* 会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产;如果店中没有产品 * 了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。 * * 分析: * 1. 是否是多线程问题?是,生产者线程,消费者线程 * 2. 是否有共享数据?是,店员(或产品) * 3. 如何解决线程的安全问题?同步机制,有三种方法 * 4. 是否涉及线程的通信?是 * * */ class Clerk{ private int productCount = 0; //生产产品 public synchronized void produceProduct() { if(productCount < 20){ productCount++; System.out.println(Thread.currentThread().getName() + ":开始生产第" + productCount + "个产品"); notify(); }else{ //等待 try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } //消费产品 public synchronized void consumeProduct() { if(productCount > 0){

深入浅出Win32多线程程序设计之线程通信

深入浅出Win32多线程程序设计之线程通信 简介 线程之间通信的两个基本问题是互斥和同步。 线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。 线程互斥是指对于共享的操作系统资源(指的是广义的"资源",而不是Windows的.res文件,譬如全局变量就是一种共享资源),在各线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。 线程互斥是一种特殊的线程同步。 实际上,互斥和同步对应着线程间通信发生的两种情况: (1)当有多个线程访问共享资源而不使资源被破坏时; (2)当一个线程需要将某个任务已经完成的情况通知另外一个或多个线程时。 在WIN32中,同步机制主要有以下几种: (1)事件(Event); (2)信号量(semaphore); (3)互斥量(mutex); (4)临界区(Critical section)。 全局变量 因为进程中的所有线程均可以访问所有的全局变量,因而全局变量成为Win32多线程通信的最简单方式。例如:

上述程序中使用全局变量和while循环查询进行线程间同步,实际上,这是一种应该避免的方法,因为: (1)当主线程必须使自己与ThreadFunc函数的完成运行实现同步时,它并没有使自己进入睡眠状态。由于主线程没有进入睡眠状态,因此操作系统继续为它调度C P U时间,这就要占用其他线程的宝贵时间周期; (2)当主线程的优先级高于执行ThreadFunc函数的线程时,就会发生globalFlag永远不能被赋值为true的情况。因为在这种情况下,系统决不会将任何时间片分配给ThreadFunc线程。 事件 事件(Event)是WIN32提供的最灵活的线程间同步方式,事件可以处于激发

基于TCP-IP协议和多线程的通信软件的设计与实现

计算机工程与设计ComputerEngineeringandDesign2010,31(7)1417?网络与通信技术? 基于TCP/IP协议和多线程的通信软件的设计与实现 刘爽1,史国友2,张远强2 (1.大连民族学院计算机学院,辽宁大连116600;2.大连海事大学航海学院,辽宁大连116026) 摘要:为实现船舶导航监控系统可靠的数据传输,通信软件服务器端利用多线程技术实现了与通信运营商转发中心和客户端的网络通信,完成了数据采集和解析功能。通信软件客户端可以与服务器建立连接并接收实时二进制数据,解析后以数据表格形式显示。重点探讨了客户端lille务器模式下基于TCP/IP协议通信的多线程实现过程,并利用时序图和活动图进行具体描述.讨论了在软件安装调试过程中如何解决客户端死机问题和客户端数据与服务器不同步.且数据过少的问题。该软件已交付使用,运行稳定可靠。 关键词:TCPflP;多线程;通信;同步;客户端/服务器 中图法分类号:TP393.09文献标识码:A文章编号:1000-7024(2010)07.1417.04 DesignandimplementationofcommunicationsoftwarebasedonTCP/IPprotocoland multithreadingtechnology LIUShuan91,SHIGuo—you2,ZHANGYuan—qian92 (1.CollegeofComputerScienceandEngineering,DalianNationalitiesUniversity,Dalianl16600,China; 2.CollegeofNavigation,DalianMaritimeUniversity,Dalian116026,China) Abstract:Toensurereliabledatatransferoftheshipnavigationandmonitoringsystem,serversideofthecommunicationsothareutilizesmultithreadingtechnologytoachievenetworkcommunicationwithtransfercenterofthecommunicationsoperatorandclient,andfinishesthefunctionsofdataacquisitionandparser.Atthesametime,clientsideofthesoftwareestablishesconnectionswithSgTVerandparsesreceivingrealtimebinarydatatodisplayindatagrid.TheemphasisofthepaperiscommunicationprocedurebetweenSel'VerandclientsbasedonTCPflPprotocolwithsequencediagramandactivitydiagramdescription.Intheprocessofinstallationanddebugging,problemsofclientbreakdownandunsynchronizeddatabetweenSerVerandclientsandlessdataintheclientsaresolved.Thesoftwarethathasbeenputintouseisstablyandreliably. Keywords:TCP/IP;multi-thread;communication;synchronization;client/server 0引言 通信软件是船舶导航监控系统的重要组成部分,集数据采集、通信、显示功能于一体,是实现水上智能交通的核心环节…。通信软件的实时性、准确性和效率直接影响船舶导航监控系统的性能。为提高通信软件的效率,可采用多线程技术,即在同一个程序中可同时执行多个任务,将整个任务分成几个线程,然后让操作系统同时执行,从而提高程序的执行效率叫。本文在分析和研究导航监控系统架构的基础上,提出了利用TCP/IP协议和多线程技术实现通信软件的方案,同时采用面向对象方法进行系统的分析和设计,使用UML2.0m作为描述工具,并给出了系统关键技术问题的解决方案和软件运行效果。络、数据库、计算机辅助调度、地理信息、无线通信、数据传输等技术汇集港口码头各类信息资源,成为处理各种海上引航、驳轮调度、集装箱运输、大型机械设备的管理平台。即利用现代化的管理手段加强港口的信息交换,实现对港口中的移动目标进行实时监控和动态管理,以此达到提高整体工作效率、促进生产管理的目的。其中的通信软件分为服务器端和客户端两部分。服务器端负责接收通信运营商转发过来的船舶AIS定位数据,解析后存库显示,建立监听Socket,接受客户端连接请求,维护客户端Socket链表,同时把接收到的二进制数据流发送到各客户端。客户端应用程序启动3个线程,包括连接线程、接收数据线程、处理数据线程完成数据的接收和解析、显示功能。图1显示了服务器和各客户端之间的物理拓扑结构。 1系统概述2通信软件总体设计 宁波港务局船舶导航监控系统应用全球定位、计算机网服务器端和客户端通信软件都采用了VisualStudio2005 收稿日期:2009.04-28;修订日期:2009.06-29。 作者简介:刘爽(1977一),女(满族),辽宁锦州人,博士,讲师,研究方向为智能交通系统、机器学习;史国友,博士,副教授,研究方向为智能交通系统;张远强,硕士研究生,研究方向为智能控制。E-maihliushuang@dlnu.edu.cn 万方数据

相关主题
相关文档
最新文档