c 实现单例模式

懒汉式,非线程安全的单例实现

懒汉式: 等用到的时候才实例化

#include <stdio.h>
#include <stdlib.h>

struct singleton_t {
    int val;
};

struct singleton_t *Instance = NULL;

struct singleton_t * getInstance() {

    if(Instance == NULL) {
        Instance = (struct singleton_t *)malloc(sizeof(struct singleton_t));
        Instance -> val = 8848;
    }
    return Instance;
}

int main() {
    struct singleton_t *inst = getInstance();
    printf("hello: %p\n",inst);
    return 0;
}

多线程同时调用getInstance()函数时,Instance可能会被初始化多次

懒汉式,线程安全

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

struct singleton_t {
    int val;
};
pthread_mutex_t lock;  // 线程锁

struct singleton_t *Instance = NULL;

struct singleton_t * getInstance() {
    pthread_mutex_lock(&lock);
    if(Instance == NULL) {
        Instance = (struct singleton_t *)malloc(sizeof(struct singleton_t));
        Instance -> val = 8848;
    }
    pthread_mutex_unlock(&lock);
    return Instance;
}

int main() {
    struct singleton_t *inst = NULL;
    if(pthread_mutex_init(&lock, NULL) != 0) {
        perror("pthread_mutex_init");
        return -1;
    } 
    inst = getInstance();
    printf("hello: %p\n",inst);
    return 0;
}

该版本加入了互斥变量来保护Instance变量的并发访问,但是每次调用getInstance()都需要加锁操作,性能太差。

性能优化的线程安全单例

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

struct singleton_t {
    int val;
};
pthread_mutex_t lock;  // 线程锁

struct singleton_t *Instance = NULL;

struct singleton_t * getInstance() {
    if(Instance == NULL) {
        pthread_mutex_lock(&lock);
        if(Instance == NULL) {
            Instance = (struct singleton_t *)malloc(sizeof(struct singleton_t));
            Instance -> val = 8848;
        }
        pthread_mutex_unlock(&lock);
    }
    return Instance;
}

int main() {
    struct singleton_t *inst = NULL;
    if(pthread_mutex_init(&lock, NULL) != 0) {
        perror("pthread_mutex_init");
        return -1;
    } 
    inst = getInstance();
    printf("hello: %p\n",inst);
    return 0;
}

典型的double check 操作。
该版本依旧有一个问题:

Instance = (struct singleton_t *)malloc(sizeof(struct singleton_t));
Instance -> val = 8848;

malloc 执行完毕后,Instance变量非空,这时其他线程就可以获取Instance变量,但是Instance还没有完成下一句的初始化操作,进一步修改成:

struct singleton_t * getInstance() {
    if(Instance == NULL) {
        pthread_mutex_lock(&lock);
        if(Instance == NULL) {
            struct singleton_t *tmp = (struct singleton_t *)malloc(sizeof(struct singleton_t));
            tmp -> val = 8848;
            Instance = tmp;
        }
        pthread_mutex_unlock(&lock);
    }
    return Instance;
}

修改完后,考虑多核并发的场景下,由于缓存可见性问题,某个线程可能看到的结果是:

    Instance = tmp;
    tmp -> val = 8848;

这时可以加入内存屏障:

struct singleton_t *getInstance() {
    rmb();  // 设置读屏障
    if (Instance == NULL) {
        pthread_mutex_lock(&lock);  // ignore error handling
        if (Instance == NULL) {
            struct singleton_t *tmp = malloc(sizeof(*Instance));
            tmp->val = 8848;
            wmb(); // 设置写屏障
            Instance = tmp;
        }
        pthread_mutex_unlock(&lock);
    }
    return Instance;
}
文档更新时间: 2021-03-12 17:12   作者:周国强