动态网站制作指南 [  QQ表情  ]
[ 投票调查 ]
[ 企业邮箱 ]
[ 网站空间 ]
网络编程 | 站长之家 | 网页制作 | 图形图象 | 操作系统 | 冲浪宝典 | 软件教学 | 网络办公 | 邮件系统 | 网络安全 | 认证考试 | 系统进程
ASP源码 | .Net源码 | PHP源码 | JSP源码 | JAVA源码 | CGI源码 | VB源码 | C++源码 | Delphi源码 | PB源码 | VF源码 | 汇编 | 服务器
Firefox | IE | Maxthon | 迅雷 | 电驴 | BitComet | FlashGet | QQ | QQ空间 | Vista | 输入法 | Ghost | Word | Excel | wps | Powerpoint
asp | .net | php | jsp | Sql | c# | Ajax | xml | Dreamweaver | FrontPages | Javascript | css | photoshop | fireworks | Flash | Cad | Discuz!
当前位置 > 网站建设学院 > 网络编程 > C/C++教程
Tag:注入,存储过程,分页,安全,优化,xmlhttp,fso,jmail,application,session,防盗链,stream,无组件,组件,md5,乱码,缓存,加密,验证码,算法,cookies,ubb,正则表达式,水印,索引,日志,压缩,base64,url重写,上传,控件,Web.config,JDBC,函数,内存,PDF,迁移,结构,破解,编译,配置,进程,分词,IIS,Apache,Tomcat,phpmyadmin,Gzip,触发器,socket
网络编程:ASP教程,ASP.NET教程,PHP教程,JSP教程,C#教程,数据库,XML教程,Ajax,Java,Perl,Shell,VB教程,Delphi,C/C++教程,软件工程,J2EE/J2ME,移动开发
文章搜索服务
邮件订阅
输入你的邮件地址,
你将不会错过任何关于:
[ C/C++教程 ]的信息

本月文章推荐
.编程特例篇——LOGO语言.
.在c++程序中重启自己的一种方法.
.数据结构算法集---C++语言实现.
.C++ 和 Delphi 的函数覆盖(Overr.
.C语言教程第二章: 数据类型.
.WinHelp API命令方法.
.使用Rational进行C++转换的技巧.
.WINDOWS键盘事件的挂钩监控原理及.
.C/C++中利用空指针简化代码,提高.
.1996年9月全国计算机等级考试二级.
.程序宝典:C++学习感想.
.C语言库函数(V类字母).
.自己的邮件自己发.
.数据结构学习(C++)之单链表.
.在Listboxes中加背景图.
.如何编写用TCP/IP的通讯程序.
.C/C++程序员请注意指针的用法.
.在RichEdit中实现超链接.
.C++批评系列:继承的本质.
.c++类的多态与虚函数的使用.

Win32下两种用于C++的线程同步类(上)

发表日期:2008-3-8 |


线程同步是多线程程序设计的核心内容,它的目的是正确处理多线程并发时的各种问题,例如线程的等待、多个线程访问同一数据时的互斥,防死锁等。Win32提供多种内核对象和手段用于线程同步,如互斥量、信号量、事件、临界区等。所不同的是,互斥量、信号量、事件都是Windows的内核对象,当程序对这些对象进行控制时会自动转换到核心态,而临界区本身不是内核对象,它是工作在用户态的。我们知道从用户态转换到核心态是需要以时间为代价的,所以假如能在用户态就简单解决的问题,就可以不必劳烦核心态了。

  这里我要说的是两种用于C++的多线程同步类,通过对这两种类的使用就可以方便的实现对变量或代码段的加锁控制,从而防止多线程对变量不正确的操作。

  所谓加锁,就是说当我们要访问某要害变量之前,都需要首先获得答应才能继续,假如未获得答应则只有等待。一个要害变量拥有一把锁,一个线程必须先得到这把锁(其实称为钥匙可能更形象)才可以访问这个变量,而当某个变量持有这把锁的时候,其他线程就不能重复的得到它,只有等持有锁的线程把锁归还以后其他线程才有可能得到它。之所以这样做,就是为了防止一个线程读取某对象途中另一线程对它进行了修改,或两线程同时对一变量进行修改,例如:

// 全局:
strUCt MyStruct ... { int a, b; } ;
MyStruct s;
// 线程1:
int a = s.a;
int b = s.b;
// 线程2:
s.a ++ ;
s.b -- ;
  假如实际的执行顺序就是上述书写的顺序那到没有什么,但假如线程2的执行打断了线程1,变为如下顺序:

int a = s.a; //线程1
s.a++; //线程2
s.b++; //线程2
int b = s.b; //线程1
  那么这时线程1读出来的a和b就会有问题了,因为a是在修改前读的,而b是在修改后读的,这样读出来的是不完整的数据,会对程序带来不可预料的后果。天知道两个程的调度顺序是什么样的。为了防止这种情况的出现,需要对变量s加锁,也就是当线程1得到锁以后就可以放心的访问s,这时假如线程2要修改s,只有等线程1访问完成以后将锁释放才可以,从而保证了上述两线程交叉访问变量的情况不会出现。

  使用Win32提供的临界区可以方便的实现这种锁:

// 全局:
CRITICAL_SECTION cs;
InitializeCriticalSection( & cs);
// 线程1:
EnterCriticalSection( & cs);
int a = s.a;
int b = s.b;
LeaveCriticalSection( & cs);
// 线程2:
EnterCriticalSection( & cs);
s.a ++ ;
s.b -- ;
LeaveCriticalSection( & cs);
// 最后:
DeleteCriticalSection( & cs);
  代码中的临界区变量(cs)就可以看作是变量s的锁,当函数EnterCriticalSection返回时,当前线程就获得了这把锁,之后就是对变量的访问了。访问完成后,调用LeaveCriticalSection表示释放这把锁,答应其他线程继续使用它。

  假如每当需要对一个变量进行加锁时都需要做这些操作,显得有些麻烦,而且变量cs与s只有逻辑上的锁关系,在语法上没有什么联系,这对于锁的治理带来了不小的麻烦。程序员总是最懒的,可以想出各种偷懒的办法来解决问题,例如让被锁的变量与加锁的变量形成物理上的联系,使得锁变量成为被锁变量不可分割的一部分,这听起来是个好主意。

  首先想到的是把锁封闭在一个类里,让类的构造函数和析构函数来治理对锁的初始化和锁毁动作,我们称这个锁为“实例锁”:

class InstanceLockBase
... {
CRITICAL_SECTION cs;
protected :
InstanceLockBase() ... { InitialCriticalSection( & cs); }
~ InstanceLockBase() ... { DeleteCriticalSection( & cs); }
} ;
  假如熟悉C++,看到这里一定知道后面我要干什么了,对了,就是继续,因为我把构造函数和析构函数都声明为保护的(protected),这样唯一的作用就是在子类里使用它。让我们的被保护数据从这个类继续,那么它们不就不可分割了吗:

struct MyStruct: public InstanceLockBase
... { … } ;
  什么?结构体还能从类继续?当然,C++中结构体和类除了成员的默认访问控制不同外没有什么不一样,class能做的struct也能做。此外,也许你还会问,假如被锁的是个简单类型,不能继续怎么办,那么要么用一个类对这个简单类型进行封装(记得Java里有int和Integer吗),要么只好手工治理它们的联系了。假如被锁类已经有了基类呢?没关系,C++是答应多继续的,多一个基类也没什么。

  现在我们的数据里面已经包含一把锁了,之后就是要添加加锁和解锁的动作,把它们作为InstanceLockBase类的成员函数再合适不过了:

class InstanceLockBase
... {
 CRITICAL_SECTION cs;
 void Lock() ... { EnterCriticalSection( & cs); }
 void Unlock() ... { LeaveCriticalSection( & cs); }
 …
} ;
  看到这里可能会发现,我把Lock和Unlock函数都声明为私有了,那么如何访问这两个函数呢?是的,我们总是需要有一个地方来调用这两个函数以实现加锁和解锁的,而且它们总应该成对出现,但C++语法本身没能限制我们必须成对的调用两个函数,假如加完锁忘了解,那后果是严重的。这里有一个例外,就是C++对于构造函数和析构函数的调用是自动成对的,对了,那就把对Lock和Unlock的调用专门写在一个类的构造函数和析构函数中:


class InstanceLock
... {
 InstanceLockBase * _pObj;
 public :
  InstanceLock(InstanceLockBase * pObj)
  ... {
   _pObj = pObj; // 这里会保存一份指向s的指针,用于解锁
   if (NULL != _pObj)
   _pObj -> Lock(); // 这里加锁
  }
  ~ InstanceLock()
  ... {
   if (NULL != _pObj)
   _pObj -> Unlock(); // 这里解锁
 }
} ;
  最后别忘了在类InstanceLockBase中把InstanceLock声明为友元,使得它能正确访问Lock和Unlock这两个私有函数:

class InstanceLockBase
... {
 friend class InstanceLock;
 …
} ;
  好了,有了上面的基础,现在对变量s的加解锁治理变成了对InstanceLock的实例的生命周期的治理了。假如我们有一个函数ModifyS中要对s进行修改,那么只要在函数一开始就声明一个InstaceLock的实例,这样整个函数就自动对s加锁,一旦进入这个函数,其他线程就都不能获得s的锁了:

void ModifyS()
... {
 InstanceLock lock ( & s); // 这里已经实现加锁了
 // some operations on s
} // 一旦离开lock对象的作用域,自动解锁
  假如是要对某函数中一部分代码加锁,只要用一对大括号把它们括起来再声明一个lock就可以了:


... {
 InstanceLock lock ( & s);
 // do something …
}

  好了,就是这么简单。下面来看一个测试。

  首先预备一个输出函数,对我们理解程序有帮助。它会在输出我们想输出的内容同时打出行号和时间:

void Say( char * text)
... {
 static int count = 0 ;
 SYSTEMTIME st;
 ::GetLocalTime( & st);
 printf( " %03d [%02d:%02d:%02d.%03d]%s " , ++ count, st.wHour, st.wMinute, st.wSecond, st.wMilliseconds, text);
}
  当然,原则上当多线程都调用这个函数时应该对其静态局部变量count进行加锁,这里就省略了。

  我们声明一个非常简单的被锁的类型,并生成一个实例:

class MyClass: public InstanceLockBase
... {} ;
MyClass mc;
  子线程的任务就是对这个对象加锁,然后输出一些信息:

DWord CALLBACK ThreadProc(LPVOID param)
... {
 InstanceLock il( & mc);
 Say( " in sub thread, lock " );
 Sleep( 2000 );
 Say( " in sub thread, unlock " );
 return 0 ;
}
  这里会输出两条信息,一是在刚刚获得锁的时间,二是在释放锁的时候,中间通过Sleep来延迟2秒。

  主线程负责开启子线程,然后也对mc加锁:

CreateThread( 0 , 0 , ThreadProc, 0 , 0 , 0 );
... {
 InstanceLock il( & mc);
 Say( " in main thread, lock " );
 Sleep( 3000 );
 Say( " in main thread, lock " );
}
  运行此程序,得到的输出如下:

001 [13:43:23.781]in main thread, lock
002 [13:43:26.781]in main thread, lock
003 [13:43:26.781]in sub thread, lock
004 [13:43:28.781]in sub thread, unlock
  从其输出的行号和时间可以清楚的看到两个线程间的互斥:当主线程恰好首先获得锁时,它会延迟3秒,然后释放锁,之后子线程才得以继续进行。这个例子也证实我们的类工作的很好。

  总结一下,要使用InstanceLock系列类,要做的就是:

  1、让被锁类从InstanceLockBase继续

  2、所有要访问被锁对象的代码前面声明InstanceLock的实例,并传入被锁对象的指针。

  附:完整源代码:

#pragma once
#include < windows.h >

class InstanceLock;

class InstanceLockBase
... {
 friend class InstanceLock;

 CRITICAL_SECTION cs;

 void Lock()
 ... {
  ::EnterCriticalSection( & cs);
 }

 void Unlock()
 ... {
  ::LeaveCriticalSection( & cs);
 }

 protected :
 InstanceLockBase()
 ... {
  ::InitializeCriticalSection( & cs);
 }

 ~ InstanceLockBase()
 ... {
  ::DeleteCriticalSection( & cs);
 }
} ;


class InstanceLock
... {
 InstanceLockBase * _pObj;
 public :
  InstanceLock(InstanceLockBase * pObj)
  ... {
   _pObj = pObj;
   if (NULL != _pObj)
    _pObj -> Lock();
  }

 ~ InstanceLock()
 ... {
  if (NULL != _pObj)
   _pObj -> Unlock();
 }
} ;
上一篇:asp.net中调用javascript函数实现多功能日期控件示例 人气:894
下一篇:使用Rational进行C++转换的技巧 人气:596
浏览全部C/C++的内容 Dreamweaver插件下载 常用网页广告代码全集
  最新网站源码 最新软件下载
2008-9-6 Movie34电影搜索引擎 v3.0
2008-9-6 wap2.0仿帝国建站喜用 v2.0
2008-9-6 免费人才招聘网 宽屏版 v3.01
2008-9-6 喜喔喔视频采集程序 v1.0 beta
2008-9-6 ASP客户管理系统
2008-9-6 主流驿站中秋祝福程序
2008-9-6 php实现msn协议的类
2008-9-5 Coppermine Photo Gallery v1.4.
2008-9-5 清松网络日记本 v2.4
2008-8-23 Mini WinMount V0.4
2008-8-23 Vista优化大师3.11正式版
2008-8-23 Wine 1.13
2008-8-23 KlipFolio 5.0 Build 5899-80
2008-8-23 Windows Sysinternals Desktops
2008-8-23 OneTap Movies1.2破解版
2008-8-23 AnnotaterPDF阅读1.1.503 破解版
2008-8-23 SoundMeter分贝测量仪 v1.0汉化破
2008-8-23 iDrum音乐节拍1.0破解版
  发表评论
姓 名: 验证码:
内 容:
站长工具:网站收录查询 | Google PR查询 | ALEXA排名查询 | CSS在线编辑器 | 广告代码 | Html转换js | js/vbs加密 | md5加密 | 进制转换
实用工具:汉字翻译拼音 | 符号对照表 | 个税计算 | 经典小工具 | 汉字简繁转换 | 普通单位换算 | 公制单位换算 | 生辰老黄历 | 国内电话区号 国家代码与域名缩写 | 文字加密解密 | 健康查询 | 万年历 | 汉字横竖排版 | 手机号码查询 | 计算器 | ip搜索
业务联系 | 广告刊登 | 频道合作 | 投稿荐稿 | 联系方式 | 加入收藏 | RSS订阅
Copyright © 2000-2008 www.knowsky.com All rights reserved | 网络实名:动态网站制作指南 | 沪ICP备05001343号
ホームページ制作 不動産検索システム 求人情報
防水工事·改修工事 フットサル大会 探偵