尝试在C++里实现 Java 的 synchronized 关键字

话说Java里有个很强大的关键字叫synchronized,可以方便的实现线程同步。今天异想天开,尝试在C++里模拟一个类似的。

  最近在学习C++的STL,看见智能指针这章节时,无不感叹利用语言的丰富特征,来各种实现各种巧妙的构思。最经典的莫过于使用栈对象构造/析构函数,来维护局部资源的初始化和释放。照着这个巧妙的方法,依样画葫芦自己也来写一个,来实现局部代码线程同步。

  Java里的synchronized有两种形式,一种是基于函数的,另种则是语块的。前者受C++的语法所限,估计是没法实现了,所以就尝试后者。
  块级语法很简单:

synchronized(syncObject) {
    // code
}

  
  因为Java所有变量都继承于Object,所以任意变量都能当作锁用。这在C++里无法简易实现,因此我们用特定的类型实例当作同步变量使用。
  先从最经典简易的同步类说起。

struct Lock : CRITICAL_SECTION {
    Lock() {
        ::InitializeCriticalSection(this);
    }

    ~Lock() {
        ::DeleteCriticalSection(this);
    }

    void Enter() {
        ::EnterCriticalSection(this);
    }

    void Leave() {
        ::LeaveCriticalSection(this);
    }
};

  这是windows下实现线程同步最常见的封装。只需声明一个Lock实例,在需要同步的代码前后分别调用Enter和Leave即可。
  既然用起来这么简单,为什么还要继续改进?显然这种方法有个很大的缺陷,如果忘了调用Leave,或者在调用之前就return/throw退出,那么就会引起死锁。
  所以,我们需要类似auto_ptr的机制,自动维护栈数据的创建和删除。就暂且称它_auto_lock吧。

struct _auto_lock {
    Lock& _lock;

    _auto_lock(Lock& lock) : _lock(lock) {
        _lock.Enter();
    }

    ~_auto_lock() {
        _lock.Leave();
    }
};

  _auto_lock通过引用一个Lock实例来初始化,并立即锁住临界区;被销毁时则释放锁。

  有了这个机制,我们再也不用担心忘了调用.Leave()。只需提供一个Lock对象,就能在当前语块自动加锁解锁。再也不用担心死锁的问题了。

Lock mylock;

void Test()
{
    // code1 ...

    // syn code
    {
        _auto_lock x(mylock);
    }

    // code2 ...
}

  进入syn code的"{"之后,_auto_lock被构造;无论用那种方式离开"}",析构函数都会被调用。
  上述代码类似的在stl和boost里都是及其常见的。利用栈对象的构造/析构函数维护局部资源,算是C++很常用的一技巧。
  我们的目标又近了一步。下面开始利用经典的宏定义,制造一颗synchronized语法糖,最终实现这样的语法:

Lock mylock;

void Test()
{
    // code1 ...

    synchronized(mylock)
    {
        // sync code
    }

    // code2 ...
}

  显然需要一个叫synchronized宏,并且在里面定义_auto_lock。

#define synchronized(lock)        ..... _auto_lock x(lock) ......

  乍一看这语法很像循环,并且要在循环内定义变量,所以用for(;;)的结构是再好不过了。

for(_auto_lock x(mylock); ; )

  不过sync code我们只需执行一次,所以还需另一个变量来控制次数。由于for里面只能声明一种类型的变量,所以我们在外面再套一层循环:

for(int _i=0; _i<1; _i++)for(_auto_lock x(mylock); _i<1; _i++)

  synchronized宏将mylock替换成上述代码,既没有违反语法,也实现相同的流程。得益于循环语法,甚至可以在synchronized内使用break来跳出同步块

  
  我们将上述代码整理下,并做个简单的测试。

#include <stdio.h>
#include <windows.h>
#include <process.h>

struct Lock : CRITICAL_SECTION {
    Lock() {
        ::InitializeCriticalSection(this);
    }

    ~Lock() {
        ::DeleteCriticalSection(this);
    }

    void Enter() {
        ::EnterCriticalSection(this);
    }

    void Leave() {
        ::LeaveCriticalSection(this);
    }
};

struct _auto_lock {
    Lock& _lock;

    _auto_lock(Lock& lock) : _lock(lock) {
        _lock.Enter();
    }

    ~_auto_lock() {
        _lock.Leave();
    }
};

#define synchronized(lock)        for(int _i=0; _i<1; _i++)for(_auto_lock lock##_x(lock); _i<1; _i++)

// ---------- demo ----------
Lock mylock;

// ---------- test1 ----------
void WaitTest(int id)
{
    printf("No.%d waiting...\n", id);

    synchronized(mylock)
    {
        Sleep(1000);
    }

    printf("No.%d done\n", id);
}

void Test1()
{
    _beginthread((void(__cdecl*)(void*))WaitTest, 0, (void*) 1);
    _beginthread((void(__cdecl*)(void*))WaitTest, 0, (void*) 2);
    _beginthread((void(__cdecl*)(void*))WaitTest, 0, (void*) 3);
}

// ---------- test2 ----------
void ThrowFunc(int id)
{
    printf("No.%d waiting...\n", id);

    synchronized(mylock)
    {
        Sleep(1000);
        throw "some err";
    }

    printf("No.%d done\n", id);
}

void ThrowTest(int id)
{
    try
    {
        ThrowFunc(id);
    }
    catch(...)
    {
        printf("%d excepted\n", id);
    }
}

void Test2()
{
    _beginthread((void(__cdecl*)(void*))ThrowTest, 0, (void*) 1);
    _beginthread((void(__cdecl*)(void*))ThrowTest, 0, (void*) 2);
    _beginthread((void(__cdecl*)(void*))ThrowTest, 0, (void*) 3);
}

// ---------- test3 ----------
void BreakTest(int id)
{
    printf("No.%d waiting...\n", id);

    synchronized(mylock)
    {
        Sleep(1000);
        break;
        Sleep(99999999);
    }

    printf("No.%d done\n", id);
}

void Test3()
{
    _beginthread((void(__cdecl*)(void*))BreakTest, 0, (void*) 1);
    _beginthread((void(__cdecl*)(void*))BreakTest, 0, (void*) 2);
    _beginthread((void(__cdecl*)(void*))BreakTest, 0, (void*) 3);
}

int main(int argc, char* argv[])
{
    printf("Wait Test. Press any key to start...\n");
    getchar();
    Test1();

    getchar();
    printf("Exception Test. Press any key to start...\n");
    getchar();
    Test2();

    getchar();
    printf("Break Test. Press any key to start...\n");
    getchar();
    Test3();

    getchar();
    return 0;
}

  

  使用语法糖除了好看外,有个最重要的功能就是可以在synchronized同步块里使用break来跳出,并且不会引起死锁,这是其他方法无法实现的。

时间: 2024-10-05 23:14:49

尝试在C++里实现 Java 的 synchronized 关键字的相关文章

从分布式锁角度理解Java的synchronized关键字

分布式锁 分布式锁就以zookeeper为例,zookeeper是一个分布式系统的协调器,我们将其理解为一个文件系统,可以在zookeeper服务器中创建或删除文件夹或文件.设D为一个数据系统,不具备事务能力,在并发状态下可能出现对单个数据同时读写.客户端A,B是数据系统D提供的客户端,能够对其读写. 几个关键角色已经登场,D是一个不提供事务行为的数据系统,其存放的数据可被读写,在单客户端条件下可以保证数据的可靠,但是在两个客户端可能并发请求时就变得不可靠,A写的数据可能被B覆盖,B读的数据可能

Java基础-synchronized关键字的用法(转载)

原文地址:http://blog.csdn.net/cq361106306/article/details/38736551 synchronized--同步 顾名思义是用于同步互斥的作用的. 这里精简的记一下它的使用方法以及意义: 当synchronized修饰?this或者非静态方法或者是一个实例的时候,所同步的锁是加在this或者实例对象引用上面的.比如a,b同为Main类的实例化对象,a调用被同步的方法,和b调用被同步的方法,没有形成互斥.但是不同线程的a对象调用被同步的方法就被互斥了.

Java中synchronized关键字理解

好记性不如烂笔头~~ 并发编程中synchronized关键字的地位很重要,很多人都称它为重量级锁.利用synchronized实现同步的基础:Java中每一个对象都可以作为锁.具体表现为以下三种形式. (1)对于普通同步方法,锁是当前实例对象. (2)对于静态同步方法,锁是当前类的Class对象. (3)对于同步方法块,锁是synchronized括号里配置的对象. 一.普通同步方法 使用synchronized关键字修饰一个普通方法,锁住的是当前实例的对象.当synchronized锁住该对

java中synchronized关键字的用法

在java编程中,经常需要用到同步,而用得最多的也许是synchronized关键字了,下面看看这个关键字的用法. 因为synchronized关键字涉及到锁的概念,所以先来了解一些相关的锁知识. java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁.线程进入同步代码块或方法的时候会自动获得该锁,在退出同步代码块或方法时会释放该锁.获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法. java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,当线程A

Java多线程synchronized关键字

synchronized关键字代表着同步的意思,在Java中被synchronized修饰的有三种情况 1.同步代码块 //锁为objsynchronized(obj){ while(true){ if(product > 0){ System.out.println(Thread.currentThread().getName()+"消费:"+product--); } } } 2.同步函数 //锁为thispublic synchronized void consume()

Java关于Synchronized关键字在不同位置使用的理解

Java中的Synchronized关键字 可以用来修饰同步方法: 像这样synchronized void f() {/*body*/} 也可以修饰同步语句块: 像这样synchronized(object){/*body*/}. 其中修饰同步方法还可以分为修饰static方法和实例方法. 其中修饰同步语句块还可以分为修饰instance变量,Object Reference对象引用,class 字面常量. 当synchronized作用在方法上时,锁住的便是对象实例(this): 所以syn

Java中synchronized关键字实现同步(二)

我们知道synchronized有两种:同步方法(synchronized method)和同步语句块(synchronized block).那么这两种有什么区别以及优缺点呢? SynchronizedMethod: 优点:代码简单清晰:易于维护 缺点:同步粒度过大,不利于并发:不够灵活,默认用本对象或者本类锁同步 Synchronizedblock  : 优点:灵活,可以使用任意对象锁:同步粒度小,并发度更高 缺点:代码更复杂,不易于维护 对比: Synchronized method: 默

Java基础-synchronized关键字的用法

顾名思义是用于同步互斥的作用的. 这里精简的记一下它的使用方法以及意义: 1. 当synchronized修饰 this或者非静态方法或者是一个实例的时候,所同步的锁是加在this或者实例对象引用上面的.比如a,b同为Main类的实例化对象,a调用被同步的方法,和b调用被同步的方法,没有形成互斥.但是不同线程的a对象调用被同步的方法就被互斥了. public synchronized void method(){   //-.   }   public void method()   {   s

java synchronized关键字

Java中synchronized关键字和对象的内置锁结合使用,用来保护代码块在并发环境下的线程安全,可以使被保护的代码块操作原子性. synchronized关键字可以用于修饰方法来保护方法内的全部代码块,可以用synchronized(对象1) 的方式保护指定代码块.(这里说一下:很多书中都说synchronized可以给对象加锁,我实在不愿意这么说,这样让我概念混淆...因为,对象内置锁是本来就存在的,不是谁加给它的,"synchronized(对象1)"我更愿意解释成:执行到此