线程的同步与互斥

多线程共享同一进程的地址空间。

优点: 线程之间通讯 就变得很容易

通过全局变量实现数据的共享和交换

缺点: 多个线程同时访问共享对象时需要引入同步和互斥的概念!!!

互斥: 同一时间 只能有一个任务(进程或线程)执行,谁先执行不确定!!

同步: 同一时间 只能有一个任务(进程或线程)执行,有顺序的执行!!

同步是 特殊的互斥!!!!

一、全局变量资源共享

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int a = 10;

void *func(void *arg)
{
    while (1)
    {
        printf("%s a = %d\n", (char *)arg, a);
        sleep(1);
    }
    char *p = "任务1结束";
    pthread_exit((void *)p);
}
void *func1(void *arg)
{
    while (1)
    {
        printf("%s a = %d\n", (char *)arg, a);
        sleep(1);
    }
    char *p = "任务2结束";
    pthread_exit((void *)p);
}

int main(int argc, char const *argv[])
{
    // 创建两个线程
    pthread_t lwp1, lwp2;

    pthread_create(&lwp1, NULL, func, "---------");
    pthread_create(&lwp2, NULL, func1, "=========");
    sleep(3);
    while (1)
    {
        a++;
        sleep(1);
    }
    return 0;
}

二、互斥锁

用于 线程间 互斥!!

互斥锁是一种简单的加锁方式,用来控制共享资源的访问。

互斥锁 主要有两种状态: 加锁(lock) 和解锁 (unlock)

同一时刻,只能有一个任务上锁 !!!其他任务阻塞 ---->访问临界资源前上锁(其他任务阻塞),访问结束 解锁(其他任务才能上锁)

死锁,访问前 上锁,访问后 不解锁!!!

三、互斥锁的操作流程

在访问共享资源前,对互斥锁进行加锁

在访问共享资源后,对互斥锁进行解锁

对互斥锁进行加锁后,任何试图再次对互斥锁加锁的任务 都会阻塞!!直到锁被释放。

四、互斥锁类型及其操作

互斥锁的数据类型: pthread_mutex_t

1.定义一把锁

pthread_mutex_t mutx;

2.初始化锁

#include <pthread.h>
//静态初始化不需要释放互斥锁,动态初始化必须释放互斥锁
//静态分配的互斥锁:
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//动态分配互斥锁:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
 const pthread_mutexattr_t *restrict attr);
功能:
   初始化互斥锁
参数:
   restrict mutex:互斥锁地址。类型是 pthread_mutex_t 。
   restrict attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL。
       可以使用宏
            PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,
            比如:pthread_mutex_t mutex =
PTHREAD_MUTEX_INITIALIZER;
            这种方法等价于使用 NULL 指定的 attr 参数调用
        pthread_mutex_init() 来完成动态初始化,不同之处在于 PTHREAD_MUTEX_INITIALIZER 宏不进行
错误检查。
返回值:
   成功返回0,成功申请的锁默认是打开的。
   失败返回非0。

使用方法

静态初始化:
pthread_mutex_t mutx = PTHREAD_MUTEX_INITIALIZER; 
动态初始化 ----> 推荐
pthread_mutex_t mutx;
pthread_mutex_init(&mutx,NULL);

3.销毁互斥锁

#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
功能:
    销毁指定的一个互斥锁。互斥锁在使用完毕后,必须要对互斥锁进行销毁,以释放资源。
参数:
    mutex:互斥锁地址。
返回值:
    成功:0
    失败:非 0 错误码

pthread_mutex_destroy(&mutx);

4.上锁:访问临界资源前

有很多资源一次只能供一个任务使用。一次仅允许一个任务使用的资源称为临界资源 ---> 许多物理设备都属于临界资源,如输入机、打印机、磁带机等。

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
功能:
    对互斥锁上锁,若互斥锁已经上锁,则调用者阻塞,直到互斥锁解锁后再上锁。
参数:
    mutex:互斥锁地址。
返回值:
    成功返回0,
    失败返回非0。

5.试着上锁

#include <pthread.h>
int pthread_mutex_trylock(pthread_mutex_t *mutex);
功能:
    调用该函数时,若互斥锁未加锁,则上锁,返回 0;
    若互斥锁已加锁,则函数直接返回失败,即 EBUSY
参数:
    mutex:互斥锁地址。
返回值:
    成功返回0
    失败返回非0。

6.解锁

#include <pthread.h>
int pthread_mutex_unlock(pthread_mutex_t *mutex);
功能:
    对指定的互斥锁解锁。
参数:
    mutex:互斥锁地址。
返回值:
    成功:0
    失败:非0 错误码

五、互斥锁的案例demo

1.多个任务之间 抢资源 不用互斥锁

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

void printf_string(void *str)
{
    char *p = (char *)str;
    int i = 0;
    while (p[i] != '\0')
    {
        printf("%c", p[i++]);
        fflush(stdout);
        sleep(1);
    }
}

void *func(void *arg)
{
    printf_string(arg);
}
void *func1(void *arg)
{
    printf_string(arg);
}

int main(int argc, char const *argv[])
{
    // 创建两个线程
    pthread_t lwp1, lwp2;

    pthread_create(&lwp1, NULL, func, "hello world?");
    pthread_create(&lwp2, NULL, func1, "nihao chengdu!");

    pthread_join(lwp1, NULL);
    pthread_join(lwp2, NULL);
    return 0;
}

2.多任务之间 使用互斥锁

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

// 定义锁
pthread_mutex_t mutx;

void printf_string(void *str)
{
    char *p = (char *)str;
    int i = 0;
    while (p[i] != '\0')
    {
        printf("%c", p[i++]);
        fflush(stdout);
        sleep(1);
    }
}

void *func(void *arg)
{
    // 上锁
    pthread_mutex_lock(&mutx);
    // 使用临界资源 标准输出
    printf_string(arg);
    // 解锁
    pthread_mutex_unlock(&mutx);
}
void *func1(void *arg)
{
    // 上锁
    pthread_mutex_lock(&mutx);
    // 使用临界资源 标准输出
    printf_string(arg);
    // 解锁
    pthread_mutex_unlock(&mutx);
}

int main(int argc, char const *argv[])
{
    // 初始化锁
    pthread_mutex_init(&mutx, NULL);
    // 创建两个线程
    pthread_t lwp1, lwp2;

    pthread_create(&lwp1, NULL, func, "hello world?");
    pthread_create(&lwp2, NULL, func1, "nihao chengdu!");

    pthread_join(lwp1, NULL);
    pthread_join(lwp2, NULL);
    // 销毁锁
    pthread_mutex_destroy(&mutx);
    return 0;
}

3.死锁:不允许

六、读写锁

如果只有两个任务,一个读 一个写!! -----> 互斥锁

如果3个或3个以上 任务, 多个读,多个写 ----> 建议用读写锁!!

1、读写锁的概念

多个任务读操作不受影响, 如果有任务写 其他多个读任务(不允许读,也不允许写), 如果多个任务在读,读的任务不受影响,但是写的任务(不允许写)。

一个任务读操作时,其它任务可以读,不能写,一个或多个任务上读锁成功,其它任务可以上读锁,不能上写锁

一个任务写操作时,其它任务不能读/写,一个任务上写锁成功时,其它任务不能上读锁和写锁

再简单点:

1)如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁。

2)如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁。

POSIX 定义的读写锁的数据类型是: pthread_rwlock_t

2.操作流程及API

1.定义锁

pthread_rwlock_t  rwlock;

2.初始化读写锁

#include <pthread.h> 
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); 
功能:
    用来初始化 rwlock 所指向的读写锁。
参数:
    rwlock: 指向要初始化的读写锁指针。
    attr:读写锁的属性指针。如果 attr 为 NULL 则会使用默认的属性初始化读写锁,否则使用指定
的 attr 初始化读写锁。
返回值:
    成功:0
    失败:非0
 
 静态初始化  
 pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER; 
    这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_rwlock_init() 来完成动态初始化,
    不同之
处在于 PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。

3.销毁读写锁

#include <pthread.h>
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
功能:
    用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由 pthread_rwlock_init() 自
动申请的资源) 。
参数:
    rwlock:读写锁指针。
返回值:
    成功:0
    失败:非 0 错误码

4.申请读锁:写会阻塞

#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
功能:
    以阻塞方式在读写锁上获取读锁(读锁定)。
    如果没有写者持有该锁,并且没有写者阻塞在该锁上,则调用线程会获取读锁。
    如果调用线程未获取读锁,则它将阻塞直到它获取了该锁。一个线程可以在一个读写
锁上多次执行读锁定。
    线程可以成功调用 pthread_rwlock_rdlock() 函数 n 次,但是之后该线程必须调用
    pthread_rwlock_unlock() 函数 n 次才能解除锁定。
参数:
    rwlock:读写锁指针。
返回值:
    成功:0
    失败:非 0 错误码

5.试着申请读锁

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
用于尝试以非阻塞的方式来在读写锁上获取读锁。
如果有任何的写者持有该锁或有写者阻塞在该读写锁上,则立即失败返回。

6.申请读锁:读写都阻塞

#include <pthread.h>
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
功能:
    在读写锁上获取写锁(写锁定)。
    如果没有写者持有该锁,并且没有写者读者持有该锁,则调用线程会获取写锁。
    如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。
参数:
    rwlock:读写锁指针。
返回值:
    成功:0
    失败:非 0 错误码

7.试着申请写锁

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    用于尝试以非阻塞的方式来在读写锁上获取写锁。
    如果有任何的读者或写者持有该锁,则立即失败返回。

8.读写锁解锁

#include <pthread.h>
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
功能:
    无论是读锁或写锁,都可以通过此函数解锁。
参数:
    rwlock:读写锁指针。
返回值:
    成功:0
    失败:非 0 错误码

3.读写锁案例demo

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
int a = 10;
pthread_rwlock_t rwlock;
void *rfunc(void *arg)
{
    while (1)
    {
        // 申请读锁
        pthread_rwlock_rdlock(&rwlock);
        // 读资源
        printf("%s a = %d\n", (char *)arg, a);
        // 解锁
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}
void *rfunc1(void *arg)
{
    while (1)
    {
        // 申请读锁
        pthread_rwlock_rdlock(&rwlock);
        // 读资源
        printf("%s a = %d\n", (char *)arg, a);
        // 解锁
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}
void *wfunc(void *arg)
{
    while (1)
    {
        // 申请写锁
        pthread_rwlock_wrlock(&rwlock);
        // 写资源
        a++;
        printf("%s a = %d\n", (char *)arg, a);
        // 解锁
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
}

int main(int argc, char const *argv[])
{
    // 初始化锁
    pthread_rwlock_init(&rwlock, NULL);

    // 创建两个线程
    pthread_t lwp1, lwp2, lwp3;

    pthread_create(&lwp1, NULL, rfunc, "线程1 读数据:");
    pthread_create(&lwp2, NULL, rfunc1, "线程2 读数据:");
    pthread_create(&lwp3, NULL, wfunc, "线程3 写入数据:");

    pthread_join(lwp1, NULL);
    pthread_join(lwp2, NULL);
    pthread_join(lwp3, NULL);

    // 销毁锁
    pthread_rwlock_destroy(&rwlock);
    return 0;
}

七、条件变量:条件判断

1.条件变量概述

与互斥锁不同,条件变量是用来等待而不是用来上锁的,条件变量本身不是锁!

条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。

通常条件变量和互斥锁同时使用。 条件变量的两个动作:

条件不满, 阻塞线程

当条件满足, 通知阻塞的线程开始工作。

条件变量的类型: pthread_cond_t。

2.条件变量的操作及API

1.定义条件变量

//定义条件变量
pthread_cond_t cond;

2.条件变量初始化

静态初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
动态初始化
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,
const pthread_condattr_t *restrict attr);
功能:
    初始化一个条件变量
参数:
    cond:指向要初始化的条件变量指针。
    attr:条件变量属性,通常为默认值,传 NULL 即可
返回值:
    成功:0
    失败:非 0 错误号

3.销毁条件变量

#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
功能:
    销毁一个条件变量
参数:
    cond:指向要初始化的条件变量指针
返回值:
    成功:0
    失败:非 0 错误

4.等待条件满足:阻塞等待条件满足

#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cond,
pthread_mutex_t *mutex);
功能:
    阻塞等待一个条件变量
        a) 阻塞等待条件变量 cond(参 1)满足
        b) 释放已掌握的互斥锁(解锁互斥量)相当于 pthread_mutex_unlock(&mutex);
            a) b) 两步为一个原子操作 ---> 不可中断。
        c) 当被唤醒,pthread_cond_wait 函数返回时,解除阻塞并重新申请获取互斥锁
pthread_mutex_lock(&mutex);
参数:
    cond:指向要初始化的条件变量指针
    mutex:互斥锁
返回值:
    成功:0
    失败:非 0 错误号

int pthread_cond_timedwait(pthread_cond_t *restrict cond,


pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
功能:
    限时等待一个条件变量
参数:
    cond:指向要初始化的条件变量指针
    mutex:互斥锁
    abstime:绝对时间
返回值:
    成功:0
    失败:非 0 错误号

5.唤醒等待在条件变量上阻塞的线程

#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
功能:
    唤醒至少一个阻塞在条件变量上的线程
参数
    cond:指向要初始化的条件变量指
返回值
    成功:0
    失败:非 0 错误号

int pthread_cond_broadcast(pthread_cond_t *cond);
功能:
    唤醒全部阻塞在条件变量上的线程
参数:
    cond:指向要初始化的条件变量指针
返回值:
    成功:0
    失败:非 0 错误号

3.条件变量的工作原理

4.条件变量案例:生产者消费者

消费者 买货物 生产者生产货物

如果 货物数量为 0 不能买

当 货物 数量超过 50 ,生产者 不生产

------> 生产者 2个 消费者 3个

生产者 做了调整,先不着急上锁 ,先判断数量 才上锁

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
// 锁
pthread_mutex_t mutx;
// 条件变量
pthread_cond_t cond;
// 产生数量
int count = 3;

// 消费者
void *xf_func(void *arg1)
{
    while (1)
    {
        // 进仓库买货物 上锁
        pthread_mutex_lock(&mutx);
        while (count == 0)
        {
            // 没有货物
            printf("%s: 仓库没货了! 等待生产\n", (char *)arg1);
            // 条件变量阻塞等待---> 1、解锁 2、等条件满足的通知 3、上锁
            pthread_cond_wait(&cond, &mutx);
        }
        // 买产品
        count -= 1;
        printf("%s 消费了1个产品,当前:%d\n", (char *)arg1, count);
        // 买完后 解锁
        pthread_mutex_unlock(&mutx);
        // 使用产品
        sleep(2);
    }
}

// 生产者
void *sc_func(void *arg)
{
    while (1)
    {
        // 判断产品数量
        while (count >= 10)
        {
            printf("产品太多:%d,暂停生产,休息2s\n", count);
            sleep(2);
        }
        // 产品数量 < 10;
        // 生产产品
        sleep(1);
        // 产品拿到仓库
        //  给仓库上锁
        pthread_mutex_lock(&mutx);
        count += 1;
        printf("%s 生产的产品已经搬到仓库,当前数量:%d\n", (char *)arg, count);

        // 通知消费者 当前仓库 有货物
        pthread_cond_broadcast(&cond);
        // 把货物搬到仓库后 解锁
        pthread_mutex_unlock(&mutx);
    }
}

int main(int argc, char const *argv[])
{
    // 如果只用一维数组  名字会覆盖
    char name1[3][32] = {0};
    char name2[2][32] = {0};
    // 初始化锁
    pthread_mutex_init(&mutx, NULL);
    // 条件变量初始化
    pthread_cond_init(&cond, NULL);
    // 创建3个消费者
    pthread_t xf[3];
    for (int i = 0; i < 3; i++)
    {
        sprintf(name1[i], "xf_%d", i);
        pthread_create(xf + i, NULL, xf_func, (void *)name1[i]);
    }
    // 创建2个生产者
    pthread_t sc[2];
    for (int i = 0; i < 2; i++)
    {
        sprintf(name2[i], "sc_%d", i);
        pthread_create(sc + i, NULL, sc_func, (void *)name2[i]);
    }

    // 回收生产者线程
    for (int i = 0; i < 2; i++)
    {
        pthread_join(sc[i], NULL);
    }
    // 回收消费者线程
    for (int i = 0; i < 3; i++)
    {
        pthread_join(xf[i], NULL);
    }
    // 销毁锁
    pthread_mutex_destroy(&mutx);
    // 销毁条件变量
    pthread_cond_destroy(&cond);
    return 0;
}

  • 13
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值