C语言多线程学习记录(二)

[复制链接]
查看623 | 回复5 | 2023-9-15 20:42:30 | 显示全部楼层 |阅读模式

本章节将在上一节的基础上进一步学习线程的同步机制。C语言多线程有四种同步机制将在本节记录!

前言

首先来看一个多线程很常见的问题:多线程售票

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

int ticket_num = 10;

void * sell_ticket(void * arg) {
    int i;

    for (i = 0; i < 10; i++) {
        if(ticket_num > 0) {
            sleep(1);
            printf("%u sell %d ticket\n", pthread_self(), 10 - ticket_num + 1);
            ticket_num--;
        }
    }

    return 0;
}

int main() {
    int flag;
    int i;
    void * ans;
    pthread_t tids[4];
    for(i = 0; i < 4; i++) {
        flag = pthread_create(&tids[i], NULL, &sell_ticket, NULL);
        if (flag != 0) {
            printf("new thread failed!\n");
            return 0;
        }
    }
    sleep(10);
    for (i = 0; i < 4; i++){
        flag = pthread_join(tids[i], &ans);
        if(flag != 0) {
            printf("wait for thread return failed!\n");
            return 0;
        }
    }

    return 0;
}

编译运行结果:

root@klelee:~/c/c.biancheng.com/thread/second# gcc ticket.c -o ticket.out -lpthread
root@klelee:~/c/c.biancheng.com/thread/second# ls
ticket.c  ticket.out
root@klelee:~/c/c.biancheng.com/thread/second# ./ticket.out 
245991168 sell 1 ticket
229205760 sell 2 ticket
220813056 sell 3 ticket
237598464 sell 4 ticket
245991168 sell 5 ticket
237598464 sell 6 ticket
220813056 sell 6 ticket    // 重复售票
229205760 sell 5 ticket    // 重复售票
245991168 sell 9 ticket
237598464 sell 10 ticket
220813056 sell 11 ticket
229205760 sell 12 ticket
245991168 sell 13 ticket
root@klelee:~/c/c.biancheng.com/thread/second#

可以从上面的程序运行结果中看到,对于5号和6号票出现了重复售卖的问题。造成此类问题的根本原因在于,进程中公有资源的访问权限是完全开放的,各个线程可以随时访问这些资源,程序运行过程中很容易出现“多个线程同时访问某公共资源”的情况。

线程同步就是为了解决上面出现的问题!

Linux系统中的线程同步

在Linux系统中实现线程同步的策略有四种:互斥锁、信号量、条件变量、读写锁!

下面就对这四种策略逐一分析:

互斥锁

互斥锁实现多线程同步的核心思想是:有线程访问进程空间中的公共资源时,该线程执行“加锁”操作(将资源“锁”起来),阻止其它线程访问。访问完成后,该线程负责完成“解锁”操作,将资源让给其它线程。当有多个线程想访问资源时,谁最先完成“加锁”操作,谁就最先访问资源。

互斥锁初始化

互斥锁初始化有两种方式:

  1. 使用特定的宏进行初始化:

    pthread_mutex_t myMutex;
    myMutex = PTHREAD_MUTEX_INITIALIZER;    //初始化宏
  2. 使用互斥锁初始化函数进行初始化

    pthread_mutex_t myMutex;
    pthread_mutex_init(&myMutex, NULL);    //初始化函数

以上两种互斥锁的初始化方式都是完全等价的,其中PTHREAD_MUTEX_INITIALIZER 宏和pthread_mutex_init() 函数都是定义在pthread.h 头文件中的!两者的区别在于:

  • pthread_mutex_init() 可以自定义mutex的属性,但是一般不改动!
  • 对于malloc() 分配动态内存的互斥锁必须使用pthread_mutx_init()函数
pthread_mutex_init()

其声明格式如下:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);

涉及到两个参数:

  • pthread_mutex_t *mutex 就是我们定义的互斥锁
  • const pthread_mutexattr_t *attr 表示要更改的互斥锁的属性,不改属性的时候传值NULL即可!

它有一个int型的返回值:

  • 返回0:表示初始化成功
  • 返回非0值:表示初始化失败!

注意: 不能对一个已经初始化的互斥锁进行初始化,否则会出现无法预料的错误:

互斥锁的加锁与解锁

互斥锁加解所通常有三种方式:

int pthread_mutex_lock(pthread_mutex_t* mutex);   //实现加锁
int pthread_mutex_trylock(pthread_mutex_t* mutex);  //实现加锁
int pthread_mutex_unlock(pthread_mutex_t* mutex);   //实现解锁

其中pthread_mutex_lockpthread_mutex_trylock 的区别在于:

  • 执行 pthread_mutex_lock() 函数会使线程进入等待(阻塞)状态,直至互斥锁得到释放;
  • 执行 pthread_mutex_trylock() 函数不会阻塞线程,直接返回非零数(表示加锁失败)。

互斥锁销毁

销毁互斥锁的行为是针对通过malloc() 函数动态分配内存的互斥锁。这样的互斥锁在释放内存之前需要对互斥锁进行销毁。

// 通过malloc()函数定义一个互斥锁
pthread_mutex_t *myMutex = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t));
pthread_mutex_init(myMutex, NULL);

C语言编程中,对于malloc申请的动态内存,一定要记得及时释放,否则就会造成内存泄漏的问题,大量的内存泄漏会导致机器卡死。而对于上面的myMutex,我们在释放其内存(free)之前需要对互斥锁进行一个销毁。语法如下:

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数很简单,没什么可说的,有一个int类型的返回值:如果函数成功销毁指定的互斥锁,返回数字 0,反之返回非零数。

互斥锁应用实例

还是基于前面的售票系统,继续改进:

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

int ticket_sum = 10;
pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;

void * sell_ticket (void * args) {
    int i;
    int islock = 0;

    printf("thread id : %u\n", pthread_self());
    for (i = 0; i < 10; i++) {
        islock = pthread_mutex_lock(&myMutex);
        if(islock == 0) {  // 表示加锁成功了,可以买票了!
            if (ticket_sum >0) {
                sleep(1);
                printf("%u sell %d ticket \n", pthread_self(), 10 - ticket_sum + 1);
                ticket_sum--;
            }
            pthread_mutex_unlock(&myMutex);  // 当前线程卖出一张票之后立即解锁
        }
        // 进入下一次循环,下一次循环开始前会再次尝试拿锁,这个过程中或许会有其他的线程已经拿到锁了,这就进入了等待。等到票售完为止
    }

    return 0;

}

int main() {
    int flag;
    int i;
    void * ans;

    pthread_t tids[4];    // 定义四个线程
    for (i = 0; i < 4; i++) {    // 循环创建四个线程,线程创建之后自动开始运行
        flag = pthread_create(&tids[i], NULL, &sell_ticket, NULL);
        if (flag != 0) {
            printf("new thread %d failed!\n", i);
            return 0;
        }
    }

    sleep(10);
    for(i = 0; i < 4; i++) {
        flag = pthread_join(tids[i], &ans);
        if(flag != 0) {
            printf("wait for thread %u return failed!\n", pthread_self());
            return 0;
        }
    }

    return 0;
}

编译和运行结果

root@klelee:~/c/c.biancheng.com/thread/second# ./ticket_mutex.out 
thread id : 2548233984
thread id : 2523055872
thread id : 2531448576
thread id : 2539841280
2548233984 sell 1 ticket 
2548233984 sell 2 ticket 
2548233984 sell 3 ticket 
2548233984 sell 4 ticket 
2548233984 sell 5 ticket 
2548233984 sell 6 ticket 
2548233984 sell 7 ticket 
2548233984 sell 8 ticket 
2548233984 sell 9 ticket 
2548233984 sell 10 ticket 
root@klelee:~/c/c.biancheng.com/thread/second# 

从运行结果来看,冲突问题已经得到了解决!

信号量

信号量对于线程同步的原理就是同一时间,允许有多少个线程访问同一资源,是一个对量的限制。和互斥锁相似,信号量本质上也是一个全局变量,在POSIX 标准中,信号量用 sem_t 类型的变量表示,该类型定义在<semaphore.h>头文件中。与互斥锁的pthread_mutex_t不同的是, sem_t 可以进行算数运算,如自增自减,并且各个线程之前对sem_t 的操作互相不影响。也就是说,如果当前sem_t 的值为1,同时有两个线程对sem_t进行+1运算,那么最终sem_t的结果就是3.

信号量的初始化

定义一个信号量变量:

// 引入头文件
#include <semaphore.h>
sem_t mySem;

初始化函数如下

int sem_init(sem_t *sem, int pshared, unsigned int value);
参数解释
  • sem :就是要初始化的目标信号量
  • pshared : 表示该信号量是否可以和其他进程共享,0表示不共享,1表示共享;
  • value : 设置信号量的初始值
返回值
  • 成功: 返回 0
  • 失败: 返回 -1

信号量操作函数

int sem_post(sem_t* sem);
int sem_wait(sem_t* sem);
int sem_trywait(sem_t* sem);
int sem_destroy(sem_t* sem); 
  • sem_post() 函数的功能是:将信号量的值“加 1”,同时唤醒其它等待访问资源的线程;
  • sem_wait() 当信号量的值大于 0 时,sem_wait() 函数会对信号量做“减 1”操作;当信号量的值为 0 时,sem_wait() 函数会阻塞当前线程,直至有线程执行 sem_post() 函数(使信号量的值大于 0),暂停的线程才会继续执行;
  • sem_trywait() 函数的功能和 sem_wait() 函数类似,唯一的不同在于,当信号量的值为 0 时,sem_trywait() 函数并不会阻塞当前线程,而是立即返回 -1;
  • sem_destory() 函数用于手动销毁信号量。

以上函数返回值:

  • 成功: 0
  • 失败:-1

信号量的应用实例

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

sem_t mySem;
int ticket_sum = 10;

void * sell_ticket (void * args) {
    printf("thread is %u now!\n", pthread_self());
    int i;
    int flag;
    for(i = 0; i < 10; i++) {
        // 当信号量大于0 时,sem_wait会对信号量做减一操作
        flag = sem_wait(&mySem);
        if (flag == 0) {
            if (ticket_sum > 0) {
                printf("Thread id %u sell ticket %d \n", pthread_self(), 10 - ticket_sum + 1);
                ticket_sum--;
            }
        }
        sem_post(&mySem);    // 售出一张票之后,给信号量加1,以允许其他线程售票
        sleep(1);
    }
    return 0;
}

int main() {
    int i;
    int flag;
    void * ans;
    pthread_t tids[4];

    flag = sem_init(&mySem, 0, 1);    //信号量初始化
    if(flag != 0) {
        printf("sem init failed!\n");
        return 0;
    }

    for (i = 0; i < 4; i++) {
        flag = pthread_create(&tids[i], NULL, &sell_ticket, NULL);
        if (flag != 0) {
            printf("new thread failed!\n");
            return 0;
        }
    }
    sleep(10);
    for (i = 0; i < 4; i++) {
        flag = pthread_join(tids[i], &ans);
        if (flag != 0) {
            printf("wait for thread %u failed!\n", pthread_self());
            return 0;
        }
    }
    sem_destroy(&mySem);    // 退出之前需要手动销毁信号量
    return 0;
}

编译和运行结果:

root@klelee:~/c/c.biancheng.com/thread/second# ./ticket_sem.out 
thread is 2498430720 now!    //表示该线程进入了售票循环
Thread id 2498430720 sell ticket 1 
thread is 2506823424 now!    //表示该线程进入了售票循环
Thread id 2506823424 sell ticket 2 
thread is 2490038016 now!    //表示该线程进入了售票循环
Thread id 2490038016 sell ticket 3 
thread is 2481645312 now!    //表示该线程进入了售票循环
Thread id 2481645312 sell ticket 4 
Thread id 2498430720 sell ticket 5 
Thread id 2506823424 sell ticket 6 
Thread id 2490038016 sell ticket 7 
Thread id 2481645312 sell ticket 8 
Thread id 2498430720 sell ticket 9 
Thread id 2506823424 sell ticket 10 
root@klelee:~/c/c.biancheng.com/thread/second#

条件变量

写在前面,条件变量实现线程同步必须和互斥锁搭配使用。在具体的使用过程中需要根据需求判断到底使用哪种策略来实现线程同步。

条件变量实现同步的原理就是,如果在某个条件不成立的时候就让线程陷入阻塞状态,知道条件成立,线程开始执行。这通常需要多个线程之间通过公用的变量来实现,这个变量就是条件变量。

假设多个线程中,线程A的执行需要线程B操作的某个变量达到一定的值之后才能执行,那么线程B操作的这个变量就是条件变量。

一个条件变量可以阻塞多个线程,这些线程会组成一个等待队列。当条件成立时,条件变量可以解除线程的“被阻塞状态”。也就是说,条件变量可以完成以下两项操作:

  • 阻塞线程,直至接收到“条件成立”的信号;
  • 向等待队列中的一个或所有线程发送“条件成立”的信号,解除它们的“被阻塞”状态。

条件变量的初始化

条件变量使用pthread_cond_t 表示,该类型在pthread.h 头文件中进行定义!定义一个条件变量:

#include <pthread.h>
pthread_cond_t myCond;

初始化一个条件变量和互斥锁一样,有两种方法:

  • 初始化宏

    myCond = PTHREAD_COND_INITIALIZER;
  • 初始化函数

    pthread_cond_init(pthread_cond_t * cond, const pthread_condattr_t * attr);
    • 参数cond 表示我们定义的条件变量,如myCond
    • 参数attr 表示对条件变量属性,不需要更改默认属性的情况下,填NULL

pthread_cond_init 有一个int型的返回值,初始化成功返回0,初始化失败返回非0值。

阻塞当前进程

阻塞进程,也就是使条件变量生效,有两个函数:

int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
int pthread_cond_timedwait(pthread_cond_t* cond, pthread_mutex_t* mutex, const struct timespec* abstime); 

常用pthread_cond_wait函数,参数cond 表示我们定义的条件变量,mutex 表示和条件变量搭配使用的互斥锁。可能从概念上来说都较为抽象,后面的实例中很容易看明白的。

pthread_cond_timewait 中,有一个adbtime 的参数。abstime 参数指的是绝对时间,例如您打算阻塞线程 5 秒钟,那么首先要得到当前系统的时间,然后再加上 5 秒,最终得到的时间才是传递的实参值。

以上两个函数都能用来阻塞线程,它们的区别在于:

  • pthread_cond_wait()函数可以永久阻塞线程,直到条件变量成立的那一刻;
  • pthread_cond_timedwait() 函数只能在 abstime 参数指定的时间内阻塞线程,超出时限后,该函数将重新对互斥锁执行“加锁”操作,并解除对线程的阻塞,函数的返回值为ETIMEDOUT

返回值:如果函数成功接收到了“条件成立”的信号,重新对互斥锁完成了“加锁”并使线程继续执行,函数返回数字 0,反之则返回非零数。

接触阻塞状态

条件变量可以同时阻塞多条线程的执行,同时在接触阻塞之后也可以对多条线程进行释放。

int pthread_cond_signal(pthread_cond_t* cond);
int pthread_cond_broadcast(pthread_cond_t* cond);

很容易理解,pthread_cond_signal 函数用来释放一条线程的阻塞状态,具体线程的选择,有操作系统进行调度。而pthread_cond_broadcast 测试广播通知所有的线程接触阻塞。

返回值:函数成功解除线程的“被阻塞”状态时,返回数字 0,反之返回非零数。

销毁条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

条件变量应用实例

该实例在主线程中创建两个子线程,并初始化一个全局变量x作为线程A和线程B的共享变量,线程A作为被阻塞线程,需要等待线程B对条件变量x的操作以满足条件,从而唤醒线程A。

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

pthread_cond_t myCond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t myMutex = PTHREAD_MUTEX_INITIALIZER;
int x = 0;

void * wait_for_true (void * args) {
    int ret;

    ret = pthread_mutex_lock(&myMutex);
    if (ret != 0) {
        printf("thread %u mutex lock failed!\n", pthread_self());
        return NULL;
    }

    ret = pthread_cond_wait(&myCond, &myMutex);
    if (ret == 0) {
        printf("thread_a exec sucessful! Now x = %d \n", x);
    }
    pthread_mutex_unlock(&myMutex);
    return NULL;
}

void * do_for_cond (void * args) {
    int ret;

    while (x != 10) {
        ret = pthread_mutex_lock(&myMutex);
        if (ret == 0) {
            x++;
            printf("do_for_wait: x = %d\n", x);
            sleep(1);
            pthread_mutex_unlock(&myMutex);
        }
    }
    ret = pthread_cond_signal(&myCond);
    if (ret != 0) {
        printf("unlock cond failed!\n");
        return NULL;
    }
    return NULL;
}

int main() {
    int ret;
    pthread_t thread_a, thread_b;

    ret = pthread_create(&thread_a, NULL, wait_for_true, NULL);
    if (ret != 0) {
        printf("new thread_a failed!\n");
        return 0;
    }

    ret = pthread_create(&thread_b, NULL, do_for_cond, NULL);
    if (ret != 0) {
        printf("new thread_b failed!\n");
        return 0;
    }
    sleep(5);
    // 在这里回顾一下pthread_join的参数:int pthread_join(pthread_t thread, void ** retval);
    ret = pthread_join(thread_a, NULL);
    if (ret) {    // 如果只需要判断非零,可以直接写ret,不需要写ret != 0
        printf("wait for thread_a failed!\n");
        return 0;
    }

    ret = pthread_join(thread_b, NULL);
    if (ret) {
        printf("wait for thread_b failed!\n");
        return 0;
    }
    pthread_cond_destroy(&myCond);
    return 0;
}

编译和运行结果

root@klelee:~/c/c.biancheng.com/thread/second# gcc cond_test.c -o cond_test.out -lpthread
root@klelee:~/c/c.biancheng.com/thread/second# ./cond_test.out 
do_for_wait: x = 1
do_for_wait: x = 2
do_for_wait: x = 3
do_for_wait: x = 4
do_for_wait: x = 5
do_for_wait: x = 6
do_for_wait: x = 7
do_for_wait: x = 8
do_for_wait: x = 9
do_for_wait: x = 10
thread_a exec sucessful! Now x = 10 
root@klelee:~/c/c.biancheng.com/thread/second#

读写锁

读写锁就是当前线程可能存在两种锁状态:

读锁状态:其他线程可以继续发出读请求,但是不能进行写请求

写锁状态:其他线程不管读写都得等着

读写锁的初始化

在POSIX标准中,用pthread_rwlock_t 类型的变量表示读写锁,该类型的定义在pthread.h 头文件中。定义一个读写锁!

pthread_rwlock_t myRwlock;

初始化读写锁也有两种方案:

  • 宏赋值:

    pthread_rwlock_t myRwlock = PTHREAD_RWLOCK_INITIALIZER;
  • 初始化函数:

    int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
    • rwlock 参数用于指定要初始化的读写锁变量;
    • attr 参数用于自定义读写锁变量的属性,置为 NULL 时表示以默认属性初始化读写锁。

    返回值:当 pthread_rwlock_init() 函数初始化成功时,返回数字 0,反之返回非零数。

线程发出“读锁”请求

int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);

当读写锁处于“读锁”或者“无锁”状态时,以上两个函数都能成功获取读锁;

当读写锁处于“写锁”状态时:

  • pthread_rwlock_rdlock() 函数会阻塞当前线程,直至读写锁被释放;
  • pthread_rwlock_tryrdlock() 函数不会阻塞当前线程,直接返回 EBUSY。

返回值:以上两个函数如果能成功获取读锁,则函数返回0,否则返回非零值。

线程发出“写锁”请求

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

当读写锁处于“无锁”状态时,两个函数都能成功获得写锁;当读写锁处于“读锁”或“写锁”状态时:

  • pthread_rwlock_wrlock() 函数将阻塞线程,直至读写锁被释放;
  • pthread_rwlock_trywrlock() 函数不会阻塞线程,直接返回 EBUSY。

返回值:以上两个函数如果能成功获得写锁,函数返回数字 0,反之返回非零数。

释放读写锁

无论线程处于哪种锁状态,都可以使用下面的函数释放读写锁:

int pthread_rwlock_unlock(pthread_rwlock_t * rwlock);

返回值:当函数成功释放读写锁时,返回数字 0,反之则返回非零数。注意,由于多个线程可以同时获得“读锁”状态下的读写锁,这种情况下一个线程释放读写锁后,读写锁仍处于“读锁”状态,直至所有线程都释放读写锁,读写锁的状态才为“无锁”状态。

销毁读写锁

当读写锁不再使用时,我们可以借助如下函数将它销毁:

int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);

返回值:如果函数成功销毁指定的读写锁,返回数字 0,反之则返回非零数。

读写锁应用实例

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

int x = 0;    // 定义一个多线程共享变量

pthread_rwlock_t my_rwlock;    // 定义一个读写锁my_rwlock

void * read_thread(void * args) {    //读线程执行函数
    /*
     * 3个读线程被创建后都会来到这里
     */
    printf("%u read_thread readly\n", pthread_self());
    while(1) {
        sleep(1);
        /*
         * 由于是读锁,所以三个线程可以同时进到下面的语句执行
         */
        pthread_rwlock_rdlock(&my_rwlock);
        printf("read_thread %u get rdlock! x = %d \n", pthread_self(), x);
        sleep(1);
        pthread_rwlock_unlock(&my_rwlock);    // 每次读取完成需要释放读写锁,要不然写线程就拿不到写锁了
    }
    return NULL;
}

void * write_thread(void * args) {
    // 写线程创建之后也会来到这里,但是由于读锁可能会先被持有,所以x值不一定是从0开始的
    printf("%u write_thread readly\n", pthread_self());
    while(1) {
        sleep(1);
        pthread_rwlock_wrlock(&my_rwlock);
        x++;
        printf("write_thread %u get wrlock! x = %d \n", pthread_self, x);
        sleep(1);
        pthread_rwlock_unlock(&my_rwlock);
    } 
    return 0;
}

int main() {
    int i;

    pthread_rwlock_init(&my_rwlock, NULL);    // 初始化读写锁
    pthread_t read_threads[3];    // 定义3个读线程

    for (i = 0; i < 3; i++) {    // 创建3个读线程,并开始运行
        pthread_create(&read_threads[i], NULL, read_thread, NULL);
    }

    pthread_t write_threads;    // 定义1个写线程
    pthread_create(&write_threads, NULL, write_thread, NULL);    // 创建1个写线程,并开始运行

    // 等待所有线程返回,这里算是阻塞主线成结束的动作吧
    pthread_join(write_threads, NULL);
    for (i = 0; i < 3; i++) {
        pthread_join(read_threads[i], NULL);
    }

    pthread_rwlock_destroy(&my_rwlock);    // 程序结束时,销毁读写锁!
    return 0;
}

编译和执行结果,下面正好有两种运行结果:

# 第一次运行,可以看到write_thread 线程先拿到了锁,所以x的值从1开始
root@klelee:~/c/c.biancheng.com/thread/second# ./rwlock.out 
1478153984 write_thread readly
1486546688 read_thread readly
1494939392 read_thread readly
1503332096 read_thread readly
write_thread 1503879984 get wrlock! x = 1 
read_thread 1494939392 get rdlock! x = 1 
read_thread 1486546688 get rdlock! x = 1 
read_thread 1503332096 get rdlock! x = 1 
write_thread 1503879984 get wrlock! x = 2 
read_thread 1494939392 get rdlock! x = 2 
read_thread 1503332096 get rdlock! x = 2 
read_thread 1486546688 get rdlock! x = 2 
write_thread 1503879984 get wrlock! x = 3 
read_thread 1494939392 get rdlock! x = 3 
read_thread 1486546688 get rdlock! x = 3 
read_thread 1503332096 get rdlock! x = 3 
write_thread 1503879984 get wrlock! x = 4 
read_thread 1503332096 get rdlock! x = 4 
read_thread 1494939392 get rdlock! x = 4 
read_thread 1486546688 get rdlock! x = 4 
write_thread 1503879984 get wrlock! x = 5 
read_thread 1503332096 get rdlock! x = 5 
read_thread 1486546688 get rdlock! x = 5 
read_thread 1494939392 get rdlock! x = 5 
write_thread 1503879984 get wrlock! x = 6 
read_thread 1494939392 get rdlock! x = 6 
read_thread 1486546688 get rdlock! x = 6 
read_thread 1503332096 get rdlock! x = 6
...
## --------------------------------------------------------------------------
# 第二次运行,由于read_thread线程先拿到了锁,所以x从0开始。虽然可以看到写线程在第二行就拿到了锁,但是根据结果输出来看,还是算个读线程同时进行输出
root@klelee:~/c/c.biancheng.com/thread/second# ./rwlock.out 
1379985152 read_thread readly
1354807040 write_thread readly
1363199744 read_thread readly
1371592448 read_thread readly
read_thread 1379985152 get rdlock! x = 0 
read_thread 1363199744 get rdlock! x = 0 
read_thread 1371592448 get rdlock! x = 0 
write_thread 1380533040 get wrlock! x = 1 
^C

程序中共创建了 4 个子线程,其中 3 个线程用于读取 x 变量的值,读取变量前会先获得“读锁”。剩余的 1 个线程用于修改 x 变量的值,修改前先获得“写锁”。

通过执行结果不难看到,3 个读取 x 变量的线程总是能够同时获取到 x 变量的值,因为它们能够同时获得“读锁”并同时执行。

写在最后,关于线程同步,一定要明确一件事情,锁是针对共享资源的,而不是针对线程的!

回复

使用道具 举报

496199544 | 2023-9-16 09:16:10 | 显示全部楼层

学习打卡
回复

使用道具 举报

开发板 | 2023-9-16 20:52:58 | 显示全部楼层
打卡
回复

使用道具 举报

jkernet | 2023-9-17 22:06:35 来自手机 | 显示全部楼层
学习打卡
回复

使用道具 举报

lsrly | 2023-9-20 19:47:38 来自手机 | 显示全部楼层
学习打卡
好好学习,努力挣钱,专心
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则