Linux系统编程9:多线程同步

多线程同步主要有信号量、互斥量、条件变量和读写锁四种方式。

0. 背景

竞争

#include <stdio.h>
#include <pthread.h>
 
void* func(void* arg){
    printf("enter func\n");
    sleep(1);
    printf("do something\n");
    sleep(1);
    printf("level func\n");
}
 
int main(int argc,int argv[]){  
    pthread_t tids[3];
    int i;
    for(i=0;i<3;i++){
        pthread_create(&tids[i],NULL,func,&mutex);
    }
    for(i=0;i<3;i++){
        pthread_join(tids[i],NULL);
    }
}

1. 信号量

1.1 操作

No. 操作 函数
1 创建 int sem_init(sem_t *sem, int pshared, unsigned int value)
2 销毁 int sem_destroy(sem_t *sem)
3 阻塞等待 int sem_wait(sem_t *sem)
4 非阻塞等待 int sem_trywait(sem_t * sem)
5 触发 int sem_post(sem_t *sem)

1.1.1 创建

int sem_init(sem_t *sem, int pshared, unsigned int value)
No. 参数 含义
1 sem 信号量对象
2 pshared 信号量类型。0:线程共享;<0:进程共享
3 value 初始值
  • 返回值
No. 返回值 含义
1 0 成功
1 -1 失败

1.1.2 销毁

int sem_destroy(sem_t *sem)
No. 参数 含义
1 sem 信号量对象
  • 返回值
No. 返回值 含义
1 0 成功
2 -1 失败

1.2 等待

1.2.1 阻塞等待

int sem_wait(sem_t *sem)
No. 参数 含义
1 sem 信号量对象
  • 返回值
No. 返回值 含义
1 0 成功
2 -1 失败

1.2.2 非阻塞等待

int sem_trywait(sem_t * sem)
No. 参数 含义
1 sem 信号量对象
  • 返回值
No. 返回值 含义
1 0 成功
2 -1 失败

1.3 触发

int sem_post(sem_t *sem) 
No. 参数 含义
1 sem 信号量对象
  • 返回值
No. 返回值 含义
1 0 成功
2 -1 失败
  • 示例
    解决竞争
#include <stdio.h>
#include <semaphore.h>
#include <pthread.h>
 
void* func(void* arg){
    sem_wait(arg);
    printf("enter func\n");
    sleep(1);
    printf("do something\n");
    sleep(1);
    printf("level func\n");
    sem_post(arg);
}
 
int main(int argc,int argv[]){
    sem_t sem;
    sem_init(&sem,0,1);
     
    pthread_t tids[3];
    int i;
    for(i=0;i<3;i++){
        pthread_create(&tids[i],NULL,func,&sem);
    }
    for(i=0;i<3;i++){
        pthread_join(tids[i],NULL);
    }
    sem_destroy(&sem);
 
}

2. 互斥量

  • 比喻
    ATM取款
    toilet

2.1 分类

No. 分类 实现 特点
1 静态分配互斥量 pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER; 简单
2 动态分配互斥量 pthread_mutex_init(&mutex, NULL);pthread_mutex_destroy(&mutex); 可以设置更多的选项

2.2 操作

No. 操作 函数
1 加锁 int pthread_mutex_lock(pthread_t *mutex)
2 尝试加锁 int pthread_mutex_trylock(pthread_t *mutex)
3 解锁 int pthread_mutex_unlock(pthread_t *mutex)
  • 参数
No. 参数 含义
1 mutex 互斥锁

2.3 示例

  • 静态分配互斥量使用方式
#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER;

void* func(void* arg){
    pthread_mutex_lock(&mutex);
    printf("%ld enter func\n",pthread_self());
    sleep(1);
    printf("%ld do something\n",pthread_self());
    sleep(1);
    printf("%ld level func\n",pthread_self());
    pthread_mutex_unlock(&mutex);
}

int main(int argc,int argv[]){
    pthread_t tids[3];
    int i;
    for(i=0;i<3;i++){
        pthread_create(&tids[i],NULL,func,NULL);
    }
    for(i=0;i<3;i++){
        pthread_join(tids[i],NULL);
    }
}
  • 动态分配互斥量使用方式
#include <stdio.h>
#include <pthread.h>

void* func(void* arg){
    pthread_mutex_lock(arg);
    printf("%ld enter func\n",pthread_self());
    sleep(1);
    printf("%ld do something\n",pthread_self());
    sleep(1);
    printf("%ld level func\n",pthread_self());
    pthread_mutex_unlock(arg);
}

int main(int argc,int argv[]){
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex,NULL);

    pthread_t tids[3];
    int i;
    for(i=0;i<3;i++){
        pthread_create(&tids[i],NULL,func,&mutex);
    }
    for(i=0;i<3;i++){
        pthread_join(tids[i],NULL);
    }
    pthread_mutex_destroy(&mutex);
}

2.4 可能出现的问题

  1. 线程在解锁之前退出。
#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER;

void* func(void* arg){
    pthread_mutex_lock(&mutex);
    printf("%ld enter func\n",pthread_self());
    sleep(1);
    printf("%ld do something\n",pthread_self());
    pthread_exit(0);// 提前退出
    sleep(1);
    printf("%ld level func\n",pthread_self());
    pthread_mutex_unlock(&mutex);
}

int main(int argc,int argv[]){
    pthread_t tids[3];
    int i;
    for(i=0;i<3;i++){
        pthread_create(&tids[i],NULL,func,NULL);
    }
    for(i=0;i<3;i++){
        pthread_join(tids[i],NULL);
    }
}
  1. 线程在解锁之前被其他线程杀掉。
#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER;

void* func(void* arg){
    pthread_mutex_lock(&mutex);
    printf("%ld enter func\n",pthread_self());
    sleep(1);
    printf("%ld do something\n",pthread_self());
    sleep(1);
    printf("%ld level func\n",pthread_self());
    pthread_mutex_unlock(&mutex);
}
void* hacker(void* arg){
    sleep(1);
    pthread_t* tids = arg;
    pthread_cancel(tids[0]);
    printf("Kill TID:%ld\n",tids[0]);
}
int main(int argc,int argv[]){
    void* (*funcs[])(void*) = {func,func,func,hacker};
    pthread_t tids[4];
    int i;
    for(i=0;i<4;i++){
        pthread_create(&tids[i],NULL,funcs[i],tids);
    }
    for(i=0;i<4;i++){
        pthread_join(tids[i],NULL);
    }
}
  • 更加安全的做法
    使用pthread_cleanup,保证线程正常或者异常退出都能释放互斥锁。

线程在解锁之前退出解决方式

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

pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER;

void* func(void* arg){
    pthread_cleanup_push(pthread_mutex_unlock,&mutex);

    pthread_mutex_lock(&mutex);
    printf("%ld enter func\n",pthread_self());
    sleep(1);
    printf("%ld do something\n",pthread_self());
    pthread_exit(0);// 提前退出
    sleep(1);
    printf("%ld level func\n",pthread_self());
    pthread_mutex_unlock(&mutex);

    pthread_cleanup_pop(0);
}

int main(int argc,int argv[]){
    pthread_t tids[3];
    int i;
    for(i=0;i<3;i++){
        pthread_create(&tids[i],NULL,func,NULL);
    }
    for(i=0;i<3;i++){
        pthread_join(tids[i],NULL);
    }
}

线程在解锁之前被其他线程杀掉解决方式

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

pthread_mutex_t mutex= PTHREAD_MUTEX_INITIALIZER;

void* func(void* arg){
    pthread_cleanup_push(pthread_mutex_unlock,&mutex);
    pthread_mutex_lock(&mutex);
    printf("%ld enter func\n",pthread_self());
    sleep(1);
    printf("%ld do something\n",pthread_self());
    sleep(1);
    printf("%ld level func\n",pthread_self());
    pthread_exit(0);
    pthread_cleanup_pop(0);
}
void* hacker(void* arg){
    sleep(1);
    pthread_t* tids = arg;
    pthread_cancel(tids[0]);
    printf("Kill TID:%ld\n",tids[0]);
}
int main(int argc,int argv[]){
    void* (*funcs[])(void*) = {func,func,func,hacker};
    pthread_t tids[4];
    int i;
    for(i=0;i<4;i++){
        pthread_create(&tids[i],NULL,funcs[i],tids);
    }
    for(i=0;i<4;i++){
        pthread_join(tids[i],NULL);
    }

}

动态分配互斥量使用方式,也使用pthread_cleanup

#include <stdio.h>
#include <pthread.h>
 
void* func(void* arg){
    pthread_cleanup_push(pthread_mutex_unlock,arg);
 
    pthread_mutex_lock(arg);
    printf("%ld enter func\n",pthread_self());
    sleep(1);
    printf("%ld do something\n",pthread_self());
    sleep(1);
    printf("%ld level func\n",pthread_self());
    pthread_exit(0); //  退出才能出发clean_up
 
    pthread_cleanup_pop(0);
}
 
int main(int argc,int argv[]){
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex,NULL);
     
    pthread_t tids[3];
    int i;
    for(i=0;i<3;i++){
        pthread_create(&tids[i],NULL,func,&mutex);
    }
    for(i=0;i<3;i++){
        pthread_join(tids[i],NULL);
    }
    pthread_mutex_destroy(&mutex);
}

2.5 基本套路

在线程处理函数中使用互斥量的基本套路

pthread_cleanup_push(pthread_mutex_unlock,pmutex);
pthread_mutex_lock(pmutex);

// do something

pthread_exit(0); //  退出才能出发clean_up
pthread_cleanup_pop(0);

2.6 信号量与互斥量的区别

No. 区别 信号量 互斥量
1 使用对象 线程和进程 线程
2 量值 非负整数 0或1
3 操作 PV操作可由不同线程完成 加锁和解锁必须由同一线程使用
4 应用 用于线程的同步 用于线程的互斥
  • 互斥:主要关注于资源访问的唯一性和排他性。
  • 同步:主要关注于操作的顺序,同步以互斥为前提。

3 条件变量

  • 概念
    线程挂起直到共享数据的某些条件得到满足

3.1 分类

No. 分类 实现 特点
1 静态分配条件变量 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 简单
2 动态分配静态变量 pthread_cond_init(&cond, NULL);pthread_cond_destroy(&cond); 可以设置更多的选项

3.2 操作

No. 操作 函数
1 条件等待 int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
2 计时等待 int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
3 单个激活 int pthread_cond_signal(pthread_cond_t *cond)
4 全部激活 int pthread_cond_broadcast(pthread_cond_t *cond)

3.2.1 等待

3.2.1.1 条件等待
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
No. 参数 含义
1 cond 条件变量
2 mutex 互斥锁
3.2.1.2 计时等待
int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime)
No. 参数 含义
1 cond 条件变量
2 mutex 互斥锁
3 abstime 等待时间
  • 返回值
No. 返回值 含义
1 ETIMEDOUT 超时结束等待

3.2.2 激活

3.2.2.1 单个激活
int pthread_cond_signal(pthread_cond_t *cond)
No. 参数 含义
1 cond 条件变量
  • 返回值
No. 返回值 含义
1 0 成功
2 正数 错误码
3.2.2.2 全部激活
int pthread_cond_broadcast(pthread_cond_t *cond)
No. 参数 含义
1 cond 条件变量
  • 返回值
No. 返回值 含义
1 0 成功
2 正数 错误码

套路

条件变量一般与互斥锁一起使用。

  • 条件变量发送信号
pthread_mutex_lock(&mutex);
 
// do something
if(判断条件){
    pthread_cond_signal(&cond);// 唤醒单个
    // 或者
    pthread_cond_broadcast(&cond);// 唤醒多个
}
 
pthread_mutex_unlock(&mutex);
  • 条件变量等待信号
pthread_mutex_lock(&mutex);
 
while(判断条件){
    pthread_cond_wait(&cond,&mutex);
}
// do something
// 把判断条件改为false
pthread_mutex_unlock(&mutex);

流程分析

  • 主线程:等待子线程发信号。
  • 子线程:每隔3秒计数一次,当数字是3的倍数时通知主进程。
#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdbool.h>

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
bool condition = false;

#define LOG(msg) printf("%s:%s\n",__func__ ,msg);

void* ChildFunc(void* arg){
    int i = 0;
    while(true){
        LOG("Enter");
        pthread_mutex_lock(&mutex);
        LOG("Get Lock");
        printf("%d\n",++i);
        if(0 == i%3){
            condition = true;
            LOG("Begin Send Single");
            pthread_cond_signal(&cond);// 唤醒单个
            LOG("End Send Single");
        }
        sleep(3);
        pthread_mutex_unlock(&mutex);
        LOG("Lose Lock");
        LOG("Leave");
    }
}
void MainFunc(){
    while(true){
        LOG("Enter");
        pthread_mutex_lock(&mutex);
        LOG("Get Lock");
        while(!condition){
            LOG("Begin Wait Single");
            pthread_cond_wait(&cond,&mutex);
            LOG("End Wait Single");
        }
        condition = false;
        pthread_mutex_unlock(&mutex);
        LOG("Lose Lock");
        LOG("Leave");
    }
}

int main(){
    pthread_t tid;
    pthread_create(&tid,NULL,ChildFunc,NULL);
    MainFunc();
    pthread_join(tid,NULL);
    return 0;
}

问题:

  • 主线程在pthread_cond_wait()时,是否释放互斥锁?
  • 主线程在什么时候重新获得互斥锁?

案例

模拟放票和抢票:实现生产者消费者问题

  • 使用互斥量实现
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int num = 0;
void* GetTicket(void*){
    while(true){
        // 记录资源释放操作
        pthread_cleanup_push((void (*)(void*))pthread_mutex_unlock,&mutex);
        pthread_mutex_lock(&mutex);
        if(0<num){ // 二次检查
            --num;
            cout << pthread_self() << "取走1张票,还剩" << num << "张票" << endl;
        }else{
            cout << pthread_self() << " exit" << endl;
            pthread_exit(0);// 只能使用pthread_exit()
        }
        pthread_mutex_unlock(&mutex);
        usleep(100000); // 模拟耗时操作

        pthread_cleanup_pop(0);// 调用pthread_exit()后线程退出自动释放资源
    }
}

int main(){
    num = 100; // 必须提前存入车票
    pthread_t tids[5];
    for(int i=0;i<5;++i){
        pthread_create(tids+i,NULL,GetTicket,NULL);
    }
    // 主线程放票
    for(int i = 0;i<5;++i){
         pthread_mutex_lock(&mutex);
         num += 40;
         cout << "主线程放票"<<num<<"张,现存" << num <<"票" << endl;
         pthread_mutex_unlock(&mutex);
         usleep(100000); // 模拟耗时操作
    }

    for(int i=0;i<5;++i){
        pthread_join(tids[i],NULL);
    }
}
  • 使用条件变量实现
    使用互斥量实现的存在两个缺点
  1. 初始票数不能为0,否则抢票线程刚开始回退出。
  2. 抢票过程票数不能为0,否则抢票线程刚开始回退出。
    可以使用条件变量使抢票线程在票数为0时阻塞等待。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
struct Data{
    pthread_mutex_t* pmutex;
    pthread_cond_t* pcond;
    int* pnum;
    int* pcount;
};

void* GetTicket(void* arg){
    Data* pdata = (Data*)arg;
    while(true){
        // 记录资源释放操作
        pthread_cleanup_push((void (*)(void*))pthread_mutex_unlock,pdata->pmutex);
        pthread_mutex_lock(pdata->pmutex);
        while(0>=*(pdata->pnum)){
            if(0==*(pdata->pnum) && 0==(*pdata->pcount)){
                cout << pthread_self() << " exit" << endl;
                pthread_exit(0);// 只能使用pthread_exit()
            }
            pthread_cond_wait(pdata->pcond,pdata->pmutex);// 1.条件不满足阻塞,并且自动释放互斥锁
                                            // 2.等待board_castt信号,重新抢夺互斥锁
        }
        --*(pdata->pnum);
        cout << pthread_self() << "取走1张票,还剩" << *(pdata->pnum) << "张票" << endl;

        pthread_mutex_unlock(pdata->pmutex);
        usleep(100000); // 模拟耗时操作

        pthread_cleanup_pop(0);// 调用pthread_exit()后线程退出自动释放资源
    }
}

int main(){
    pthread_mutex_t mutex;
    pthread_mutex_init(&mutex,NULL);
    pthread_cond_t cond;
    pthread_cond_init(&cond,NULL);
    
    int num = 0;
    int count = 5;
    pthread_t tids[5];
    Data data = {&mutex,&cond,&num,&count};
    for(int i=0;i<5;++i){
        pthread_create(tids+i,NULL,GetTicket,&data);
    }
    // 主线程放票
    while(count--){
         pthread_mutex_lock(&mutex);
         num += 20;
         cout << "主线程放票" << num << "张,现存" << num <<"票" << endl;
         pthread_mutex_unlock(&mutex);
         pthread_cond_broadcast(&cond);// 唤醒所有在wait线程
         sleep(1); // 模拟耗时操作
    }

    for(int i=0;i<5;++i){
        pthread_join(tids[i],NULL);
    }
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
}

老板与会计的约定:每笔超过1000元的支出必须老板批准同意,低于1000元的会计可以自行决定。

#include <stdio.h>
#include <pthread.h>
#include <signal.h>
 
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
 
int currency = 0 ;
int signal_cnt = 0;
 
void* checkout(void* arg){
    sleep(1);
    for(;;){
        pthread_mutex_lock(&mutex);
        printf("checkout enter\n");
        currency = rand()%2000;     
        printf("spend %d\n",currency);
        if(currency >= 1000){
            printf("\033[42;31msignal boss:%d\033[0m\n");
            pthread_cond_signal(&cond);
            signal_cnt++;
        }
        printf("checkout leave\n");
        pthread_mutex_unlock(&mutex);
        //sched_yield();
        sleep(1);
    }
}
 
void* boss(void* arg){
    for(;;){
        pthread_mutex_lock(&mutex);
        printf("boss enter\n");
        while(currency < 1000){
            printf("boss wait\n");
            pthread_cond_wait(&cond,&mutex);
        }
        signal_cnt--;
        printf("\033[46;31mboss agress:%d signal_cnt:%d\033[0m\n",currency,signal_cnt);
        currency = 0;
        printf("boss leave\n");
        pthread_mutex_unlock(&mutex);
    }
}
 
int main(){
    typedef void*(*func_t)(void*);
    func_t funcs[2] = {boss,checkout};
    pthread_t tids[2];
    int i;
    pthread_setconcurrency(2);
    for(i=0;i<2;i++){
        pthread_create(&tids[i],NULL,funcs[i],tids);
    }
    for(i=0;i<2;i++){
        pthread_join(tids[i],NULL);
    }   
}

问题

  • 互斥锁和条件变量能否不作为全局变量?

4. 读写锁

资源访问分为两种情况:读操作和写操作。

读写锁比mutex有更高的适用性,可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁。

  1. 当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞;
  2. 当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行枷锁的线程将阻塞;
  3. 当读写锁在读模式锁状态时,如果有另外线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求,这样可以避免读模式锁长期占用,而等待的写模式锁请求长期阻塞;
    这种锁适用对数据结构进行读的次数比写的次数多的情况下,因为可以进行读锁共享。
  • 比喻

新闻发布会
领导发言与聊天

  • 概念
    共享独占
    读取锁(共享)
    写入锁(独占)

4.1 分类

No. 分类 实现 特点
1 静态分配读写锁 pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER 简单
2 动态分配读写锁 pthread_rwlock_init(&rwlock, NULL);pthread_rwlock_destroy(&rwlock); 可以设置更多的选项

4.2 操作

4.2.1 加锁

4.2.1.1 读取锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);

4.2.1.2 写入锁

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);

4.2.2 解锁

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
  • 示例

火车票的查询与购买

#include <stdio.h>
#include <pthread.h>
 
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
int count = 10000;
int put_cur = 0;
void* search(void* arg){
    for(;;){
        pthread_rwlock_rdlock(&rwlock);
        printf("leave %d\n",count); 
        usleep(500000);
        pthread_rwlock_unlock(&rwlock);
    }
}
void rollback(void* arg){
    count -= put_cur;
    printf("rollback %d  to %d\n",put_cur,count);
    pthread_rwlock_unlock(&rwlock);
}
void* put(void* arg){
    pthread_cleanup_push(rollback,NULL);
    for(;;){
        pthread_rwlock_wrlock(&rwlock);
        put_cur = rand()%1000;
        count += put_cur;
        printf("put %d ok,leave %d\n",put_cur,count);
        usleep(500000);
        pthread_rwlock_unlock(&rwlock);
    }
    pthread_cleanup_pop(0);
}
void* hacker(void* arg){
    sleep(2);
    pthread_t* ptids = arg;
    pthread_cancel(ptids[0]);
    printf("\033[41;34mcancel %lu\033[0m\n",ptids[0]);
}
void* get(void* arg){
    for(;;){
        pthread_rwlock_wrlock(&rwlock);
        int cur = rand()%1000;
        if(count>cur){
            count -= cur;
            printf("crash %d leave %d\n",cur,count);
        }else{
            printf("leave not enought %d\n",count);
        }
        usleep(500000);
        pthread_rwlock_unlock(&rwlock);
    }
}
 
int main(){
    pthread_t tids[4];
    typedef void*(*func_t)(void*);
    func_t funcs[4] = {put,get,search,hacker};
    int i=0;
    pthread_setconcurrency(4);
    for(i=0;i<4;i++){
        pthread_create(&tids[i],NULL,funcs[i],tids);
    }
    for(i=0;i<4;i++){
        pthread_join(tids[i],NULL);
    }
}

四个线程:一个查看、一个放票、一个取票、一个随机破坏。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容

  • 第三章 Java内存模型 3.1 Java内存模型的基础 通信在共享内存的模型里,通过写-读内存中的公共状态进行隐...
    泽毛阅读 4,287评论 2 22
  • 线程池ThreadPoolExecutor corepoolsize:核心池的大小,默认情况下,在创建了线程池之后...
    irckwk1阅读 674评论 0 0
  • 一段情感里两个人走向婚姻的殿堂是否预示着缘分已落地生根?还是一起携手从韶华到白发才算是?似乎关于爱情的话题永远都...
    艾特我和你阅读 332评论 0 1
  • 一年有四季,所以四个季节的太阳是不一样的。 我画了个彩色的太阳,挂在春天的天空,春回大地,万物复苏...
    小王子WXN阅读 258评论 0 1
  • 我是两个孩的孩妈,忙并快乐着,一直以来都想写一些自己的感悟与大家分享,今天无意中与《简书》有缘,希望在以后的交...
    知足常乐_a6bd阅读 152评论 0 0