系统线程-多线程2-互斥量、信号量、条件变量

多线程2 - 线程的同步和互斥

目录

多线程2 - 线程的同步和互斥

引入

互斥锁

使用锁的步骤

1、创建锁 初始化一把锁

2、加锁

3、解锁

4、尝试上锁函数

5、销毁锁

信号量

1、初始化一个信号量

2、P操作 -- 对信号量进行消耗

3、V操作 -- 对信号量进行还原

4、尝试进行抢占信号量操作

5、销毁信号量

条件变量

1、初始化条件变量

2、等待条件变量

3、发送条件变量

4、销毁条件变量


引入

alt text

alt text

-- 发现有时候结果为 5w 有时候小于 5w
这是线程在同时使用共享资源时 发生的抢占问题
我们需要使用线程的同步和互斥来解决这个问题
实现线程对资源的独占

-- 实现线程的同步和互斥一共有三种方式

  • 1 锁
  • 2 信号量
  • 3 条件变量

互斥锁

-- 互斥锁可以实现线程对资源的独占

使用锁的步骤

1、创建锁 初始化一把锁

-- 函数头文件

  • #include <pthread.h>

-- 函数的原型:

-- 使用函数创建为动态的创建互斥锁

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

-- 静态的创建锁

  • pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

是将PTHREAD_MUTEX_INITIALIZER赋值给定义的互斥量
但这个方法没办法设置互斥量的属性,也不适用于动态分配的互斥量,比较少用。

-- 函数的作用

  • 该函数用于 C 函数的多线程编程中,互斥锁的初始化。

-- 函数的参数:

  • pthread_mutex_t * mutex:填写要进行初始化的锁的对象
    例如:pthread_mutex_t lock; 该参数填写:&lock
  • pthread_mutexattr_t *restrict attr:初始化的锁的属性,给 NULL 则使用默认的互斥锁属性,默认属性为快速互斥锁。

互斥锁的属性在创建锁的时候指定,在 LinuxThreads 实现中仅有一个锁类型属性,不同的锁类型在试图对一个已经被锁定的互斥锁加锁时表现不同。

-- 函数返回值:

  • pthread_mutexattr_init() 函数成功完成之后会返回零,其他任何返回值都表示出现了错误。
  • 函数成功执行后,互斥锁被初始化为未锁住态。

-- 不论哪种类型的锁,都不可能被两个不同的线程同时得到,而必须等待解锁。

2、加锁

-- 函数头文件

  • #include <pthread.h>

-- 函数的原型:

  • int pthread_mutex_lock(pthread_mutex_t *mutex);

-- 函数的作用

  • 该函数用于 C 函数的多线程编程中,用于给指定的互斥量加锁。

对参数所填写的锁 进行上锁操作
锁处于解锁状态 会上锁成功 函数不会阻塞
锁处于上锁状态 会阻塞等待锁解锁 然后再进行上锁操作解除阻塞

-- 函数的参数:

  • pthread_mutex_t * mutex:填写要进行加锁的锁的对象
    例如:pthread_mutex_t lock; 该参数填写:&lock

-- 函数返回值:

  • 成功返回零
  • 失败返回一个 非零值
3、解锁

-- 函数头文件

  • #include <pthread.h>

-- 函数的原型:

  • int pthread_mutex_unlock(pthread_mutex_t *mutex);

-- 函数的作用

  • 该函数用于 C 函数的多线程编程中,用于给指定的互斥量解锁。

注意对一把锁进行加锁后 一定要对其进行解锁操作 否则可能出现死锁的情况

-- 函数的参数:

  • pthread_mutex_t * mutex:填写要进行解锁的锁的对象
    例如:pthread_mutex_t lock; 该参数填写:&lock

-- 函数返回值:成功返回零

  • 失败返回一个 非零值
4、尝试上锁函数

-- 函数头文件

  • #include <pthread.h>

-- 函数的原型:

  • int pthread_mutex_trylock(pthread_mutex_t *mutex);

-- 函数的作用

  • 该函数用于 C 函数的多线程编程中,用于给指定的互斥量尝试加锁。

对参数所填写的锁 进行上锁操作
锁处于解锁状态 会上锁成功 函数不会阻塞
锁处于上锁状态 该函数不会阻塞 会立马返回一个值 16         -- EBUSY

-- 函数的参数:

  • pthread_mutex_t * mutex:填写要进行加锁的锁的对象
    例如:pthread_mutex_t lock; 该参数填写:&lock

-- 函数返回值:

  • 成功返回零
  • 失败返回16

alt text

5、销毁锁

-- 函数头文件

  • #include <pthread.h>

-- 函数的原型:

  • int pthread_mutex_destroy(pthread_mutex_t *mutex);

-- 函数的作用

  • 该函数用于 C 函数的多线程编程中,用于销毁指定的互斥量。

当我们不在需要锁的时候 需要对锁进行销毁 锁会失去原本的作用

-- 函数的参数:

  • 成功返回0
  • 失败返回一个非零值

1、使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量无须销毁。
2、不要销毁一个已加锁的互斥量, 或者是真正配合条件变量使用的互斥量。
3、已经销毁的互斥量, 要确保后面不会有线程再尝试加锁。

-- 这里注册函数就有了意义,如果在上锁完,还没有进行解锁,线程就结束的话,可以利用注册函数,在注册函数中写解锁的操作,这样就算线程结束,锁也会被解锁。


alt text


信号量

-- linux系统中提供了两个信号量实现,一种是System V信号量,另一种是POSIX信号量,它们的作用是相同的,都是用于同步进程之间及线程之间的操作,以达到无冲突地访问共享资源的目的。

alt text

-- 今天讲的(也就是下午所讲的)就是无名信号量

-- 无名信号量, 又称为基于内存的信号量,由于其没有名字,没法通过open操作直接找到对应的信号量,所以很难直接用于没有关联的两个进程之间。
无名信号量多用于线程之间的同步。因为线程会共享地址空间, 所以访问共同的无名信号量是很容易办到的事情

1、初始化一个信号量

-- 函数头文件

  • #include <semaphore.h>

-- 函数的原型:

  • int sem_init(sem_t *sem, int pshared, unsigned int value);

-- 函数的作用

  • 该函数用于 C 函数的多线程编程中,用于初始化一个信号量。

-- 函数的参数:

  • sem_t *sem:填写要进行初始化的信号量对象
    例如:sem_t sem; 该参数填写:&sem
  • int pshared:
    如果 pshared 的值为 0,那么信号量在线程的地址空间内;(表示在线程间使用)否则该信号量是进程间共享的。
  • unsigned int value:信号量的初始值。

-- 函数返回值:

  • 成功返回0
  • 失败返回一个非零值

2、P操作         -- 对信号量进行消耗

-- 函数头文件

  • #include <semaphore.h>

-- 函数的原型:

  • int sem_wait(sem_t *sem);

-- 函数的作用

  • 该函数用于 C 函数的多线程编程中,用于给指定的信号量进行P操作。

对信号量进行消耗操作 -1
信号量的值>0 会对信号量进行-1 操作
信号量的值=0 会阻塞直到该信号量可以进行-1 操作

-- 函数的参数:

  • sem_t *sem:填写要进行P操作的信号量对象
    例如:sem_t sem; 该参数填写:&sem

-- 函数返回值:

  • 成功返回0
  • 失败返回一个非零值

3、V操作         -- 对信号量进行还原

-- 函数头文件

  • #include <semaphore.h>

-- 函数的原型:

  • int sem_post(sem_t *sem);

-- 函数的作用

  • 该函数用于 C 函数的多线程编程中,用于给指定的信号量进行V操作。

还原一个信号量 进行+1 操作

-- 函数的参数:

  • sem_t *sem:填写要进行V操作的信号量对象
    例如:sem_t sem; 该参数填写:&sem

-- 函数返回值:

  • 成功返回0
  • 失败返回一个非零值

4、尝试进行抢占信号量操作

-- 函数头文件

  • #include <semaphore.h>

-- 函数的原型:

  • int sem_trywait(sem_t *sem);

-- 函数的作用

  • 该函数用于 C 函数的多线程编程中,用于给指定的信号量进行尝试抢占操作。

当信号量的值>0 进行-1 操作 当信号量的值=0 时 该函数会立马返回

5、销毁信号量

-- 函数头文件

  • #include <semaphore.h>

-- 函数的原型:

  • int sem_destroy(sem_t *sem);

-- 函数的作用

  • 该函数用于 C 函数的多线程编程中,用于销毁指定的信号量。

当我们不再使用信号量时 会对信号量进行摧毁操作

-- 函数的参数:

  • sem_t *sem:填写要进行销毁的信号量对象
    例如:sem_t sem; 该参数填写:&sem

-- 函数返回值:

  • 成功返回 0
  • 失败返回 -1

alt text


条件变量

-- 多线程编程中,涉及到线程并发,因此也衍生了一些问题,常见的有2类问题:
1、多个线程访问同一个共享资源,这个可以用互斥量来解决;
2、某些线程运行后,有时需要等待一定的条件才会继续执行,如果条件不满足就会等待,而条件的达成, 很可能取决于另一个线程。

-- 条件变量主要是解决上面的第二个问题。条件变量用来阻塞一个线程,直到某条件满足为止。通常条件变量和互斥锁同时使用。

-- 条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。

-- 为什么要用条件变量?

  • 因为如果不使用条件变量,线程就需要 轮询+休眠 来查看是否满足条件,这样严重影响效率。

1、初始化条件变量

-- 函数头文件

  • #include <pthread.h>

-- 函数的原型:

  • int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);

-- 函数的作用

  • 该函数用于 C 函数的多线程编程中,用于初始化一个条件变量。

-- 函数的参数:

  • pthread_cond_t *cond:填写要进行初始化的条件变量对象
    例如:pthread_cond_t cond; 该参数填写:&cond

  • const pthread_condattr_t *attr:填写条件变量的属性,如果为NULL,则使用默认属性。

-- 函数返回值:

  • 成功返回0
  • 失败返回一个非零值

-- 也可以使用定义初始化条件变量

  • pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

2、等待条件变量

-- 让指定的条件变量进入等待状态,其工作机制是先解锁传入的互斥量,再让条件变量等待,从而使所在线程处于阻塞状态。函数返回时,系统会确保该线程再次持有互斥量(加锁)。

-- 函数头文件

  • #include <pthread.h>

-- 函数的原型:

  • int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);

-- 函数的作用

  • 阻塞当前线程 等待其他线程发送条件变量

-- 函数的参数:

  • pthread_cond_t *cond:填写要进行等待的条件变量对象
    例如:pthread_cond_t cond; 该参数填写:&cond

  • pthread_mutex_t *mutex:填写互斥锁对象
    例如:pthread_mutex_t mutex; 该参数填写:&mutex

该锁的作用:用来确保只有当前线程正在使用共享资源

-- 函数返回值:

  • 成功返回0
  • 失败返回一个非零值

-- pthread_cond_wait函数返回时,系统会确保该线程再次持有互斥量(加锁)。

3、发送条件变量

-- 函数头文件

  • #include <pthread.h>

-- 函数的原型:

  • int pthread_cond_signal(pthread_cond_t *cond);

-- 函数的作用

  • 告知等待条件变量的线程 条件已满足 可以解除阻塞继续向下运行

-- 函数的参数:

  • pthread_cond_t *cond:填写要进行发送的条件变量对象
    例如:pthread_cond_t cond; 该参数填写:&cond

-- 函数返回值:

  • 成功返回0
  • 失败返回一个非零值

4、销毁条件变量

-- 函数头文件

  • #include <pthread.h>

-- 函数的原型:

  • int pthread_cond_destroy(pthread_cond_t *cond);

-- 函数的作用

  • 该函数用于 C 函数的多线程编程中,用于销毁指定的条件变量。

-- 函数的参数:

  • pthread_cond_t *cond:填写要进行销毁的条件变量对象
    例如:pthread_cond_t cond; 该参数填写:&cond

-- 函数返回值:

  • 成功返回 0
  • 失败返回 -1

1、使用PTHREAD_COND_INITIALIZE静态初始化的条件变量,不需要被销毁。
2、要调用pthread_cond_destroy销毁的条件变量可以调用pthread_cond_init重新进行初始化。
3、不要引用已经销毁的条件变量, 这种行为是未定义的。

-- 使用条件变量等待时,会使用一个互斥量进行加锁,加锁的临界区包含了“①查看是否满足条件、②调用pthread_cond_wait进入条件等待”这两个操作。

alt text

#include "stdio.h"
#include "pthread.h"
#include "string.h"
#include "unistd.h"
char subway[20] = {0};

pthread_mutex_t lock;
pthread_cond_t cond;

void * func(void *arg)
{
    while(1)
    {
        printf("地铁到达郑州大学\n");
        pthread_mutex_lock(&lock);
        strcpy(subway,"郑州大学站");
        pthread_mutex_unlock(&lock);
        sleep(2);
        printf("地铁到达梧桐街\n");
        pthread_mutex_lock(&lock);
        strcpy(subway,"梧桐街");
        //发送条件变量
        pthread_cond_signal(&cond);

        pthread_mutex_unlock(&lock);
        sleep(5);
        printf("地铁到达五一公园\n");
        pthread_mutex_lock(&lock);
        strcpy(subway,"五一公园");
        pthread_mutex_unlock(&lock);
        sleep(3);
    }
}


int main()
{
    //初始化锁
    pthread_mutex_init(&lock,NULL);
    //初始化条件变量
    pthread_cond_init(&cond,NULL);

    pthread_t id;
    pthread_create(&id,NULL,func,NULL);
    printf("\t还没到6点\n");
    sleep(8);
    printf("\t下班\n");
    sleep(2);
    printf("\t到达梧桐街地铁站\n");

    pthread_mutex_lock(&lock);
    if(strcmp(subway,"梧桐街") == 0)
    {
        printf("\t上车成功\n");
        //pthread_mutex_unlock(&lock);
    }
    else
    {
        printf("\t等地铁ing\n");
        //等待条件变量
        pthread_cond_wait(&cond,&lock);
        printf("\t地铁到达上车!!!\n");
        
    }
    pthread_mutex_unlock(&lock);
    pthread_cond_destroy(&cond);
    
    while(1)
    {}

    return 0;
}


alt text

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值