• linux 线程同步


    线程同步

    数据混乱的原因:

    • 资源共享
    • 调度随机
    • 线程间缺乏必要的同步机制(由cpu分配)

    锁的使用:

    建议锁!对公共数据进行保护。 所有线程应该在访问公共数据前先拿锁再访问。但是锁不具备强制性。

    互斥锁

    使用mutex(互斥量,互斥锁)的一般步骤

    1. pthread_mutex lock 创建锁
    2. pthread_mutex_init 初始化
    3. pthread_mutex_lock 加锁
    4. 访问共享数据
    5. pthread_mutex_unlock 解锁
    6. pthread_mutex_destory 销毁锁

    使用锁的注意事项

    • 尽量保证锁的粒度,越小越好,即使用共享数据前加锁, 访问结束以后立即解锁
    • 互斥锁的本质为结构体, 可以想象为整形, 初值为 1(即pthread_init函数调用成功)
      • 加锁: --操作
      • 解锁: ++操作
      • try锁: 尝试加锁, 成功则–, 失败则返回错误号EBUSY

    初始化互斥变量:

    1. pthread_mutex_init() 动态初始化
    2. pthread_mutex_t cond = PTHREAD_MUTEX_INITIALIZER; 静态初始化

    pthread_mutex_init函数

    作用: 互斥锁的初始化
    int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
    参数:
    	mutex: 传出参数, 用于初始化的锁
    	attr: 互斥量属性, 默认为NULL
    返回值:
    	成功: 0
    	失败: 错误值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    restrict的作用:

    • 用来修饰指针
    • 表示当前指针指向的那个地址空间只能由当前的指针能操作, 其他的指针不能操作

    pthread_mutex_destroy函数

    作用: 销毁一个互斥锁
    int pthread_mutex_destroy(pthread_mutex_t *mutex);
    参数:
    	mutex: 待销毁的锁
    返回值:
    	成功: 0
    	失败: 错误值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    pthread_mutex_lock函数

    作用: 给指定的锁上锁, 若锁已经被上, 则阻塞线程
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    参数:
    	mutex: 待上锁的锁
    返回值:
    	成功: 0
    	失败: 错误值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    pthread_mutex_unlock函数

    作用: 给指定的锁解锁, 同时唤醒阻塞在锁上的线程
    int pthread_mutex_lock(pthread_mutex_t *mutex);
    参数:
    	mutex: 待解锁的锁
    返回值:
    	成功: 0
    	失败: 错误值
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    pthread_mutex_trylock函数

    作用: 尝试给指定的锁加锁(不阻塞)
    int pthread_mutex_trylock(pthread_mutex_t *mutex);
    参数:
    	mutex: 待加锁的锁
    返回值:
    	成功上锁: 0
    	该锁已经被上: 错误号(EBUSY)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    死锁

    • 是使用锁不恰当锁导致的一种现象
      • 对一个锁反复lock
      • 两个线程各自持有一把锁, 且去请求另一把

    读写锁

    • 锁只有一把,以读的方式给数据加锁–读锁, 以写的方式给数据加锁–写锁
    • 读共享,写独占,若读锁和写锁一起来了, 则优先写, 再读
    • 写锁的优先级高
    • 相较于互斥量而言, 当读线程多的时候, 可以提高访问效率

    主要函数

    int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
    
    int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
    
    int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
    
    int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
    int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
    
    int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    用法和mutex函数类似

    条件变量

    • 本身不是锁的一种, 通常要结合互斥锁来使用

    主要函数

    pthread_cond_init
    pthread_cond_destroy
    pthread_cond_wait			// 等待某一个条件满足
    pthread_cond_timedwait		// 等待某一个条件满足, 并设置等待的时间
    pthread_cond_signal			// 一次通知至少一个阻塞在条件变量上的线程
    pthread_cond_broadcast		// 一次通知一堆阻塞在条件变量上的线程
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    初始化条件变量:

    1. pthread_cond_init() 动态初始化
    2. pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 静态初始化

    pthread_cond_init函数

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

    pthread_cond_destroy函数

    int pthread_cond_destroy(pthread_cond_t *cond);
    
    • 1

    用法与互斥锁基本一直


    pthread_cond_wait函数

    作用:
    	1. 阻塞等待条件变量cond满足
    	2. 释放已经掌握的互斥锁(pthread_mutex_unlock)
    	===========1,2两步为一个原子操作============
    	3. 当被唤醒的时候, pthread_cond_wait函数返回时, 解除阻塞并重新申请获取互斥锁(pthread_mutex_lock)
    int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    生产者,消费者模型

    公共步骤:

    1. 创建锁pthread_mutex_t mutex
    2. 初始化pthread_mutex_init()
    3. 加锁pthread_mutex_lock
    消费者生产者
    1. 等待条件满足pthread_cond_wait()1. 生产数据
    2. 访问共享数据2. 加锁 pthread_mutex_lock()
    3.循环3. 将数据放到公共区域
    4. 解锁pthread_mutex_unlock()
    5.通知阻塞在条件上的线程
    6.循环
    1. 解锁,释放条件变量, 释放锁

    生产者,消费者模型代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <pthread.h>
    #include <unistd.h>
    
    void err_thread(int ret, const char *str)
    {
        if (ret != 0)
        {
            fprintf(stderr, "%s:%s\n", str, strerror(ret));
            pthread_exit(NULL);
        }
    }
    
    struct msg
    {
        int num;
        struct msg *next;
    };
    
    struct msg *head;
    pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  // 定义,初始化一个互斥量
    pthread_cond_t has_data = PTHREAD_COND_INITIALIZER; // 定义,初始化一个条件变量
    
    void *producer(void *arg)
    {
        while (1)
        {
            struct msg *mp = (struct msg *)malloc(sizeof(struct msg));
    
            mp->num = rand() % 1000 + 1; // 模拟生产了一个数据
            printf("--produce %d\n", mp->num);
    
            pthread_mutex_lock(&mutex); // 加锁 互斥量
            mp->next = head;            // 写公区域的操作
            head = mp;
            pthread_mutex_unlock(&mutex); // 解锁 互斥量
    
            pthread_cond_signal(&has_data); // 唤醒阻塞在has_data上的线程
    
            sleep(rand() % 3);
        }
        return NULL;
    }
    
    void *consumer(void *arg)
    {
        while (1)
        {
            struct msg *mp;
            pthread_mutex_lock(&mutex); // 加锁 互斥量
            while (head == NULL)                        // 当有多个消费者的时候,要用while,防止被唤醒以后线程没有资源
            {
                pthread_cond_wait(&has_data, &mutex);   // 阻塞等待条件变量, 解锁
            }                                           // 返回时会重新加锁mutex
    
            mp = head;
            head = mp->next;
    
            pthread_mutex_unlock(&mutex); // 解锁 互斥量
            printf("--------consumer id: %lu :%d\n", pthread_self(), mp->num);
    
            free(mp);
            sleep(rand() % 3);
        }
        return NULL;
    }
    
    int main()
    {
        int ret;
        pthread_t pid, cid;
    
        srand(time(NULL));
    
        ret = pthread_create(&pid, NULL, producer, NULL);
        if (ret != 0)
            err_thread(ret, "pthread_create produser error");
    
        ret = pthread_create(&cid, NULL, consumer, NULL);
        if (ret != 0)
            err_thread(ret, "pthread_create consumer error");
    
        ret = pthread_create(&cid, NULL, consumer, NULL);
        if (ret != 0)
            err_thread(ret, "pthread_create consumer error");
    
        pthread_join(pid, NULL);
        pthread_join(cid, NULL);
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94

    信号量

    • 相当于初始化值为N的互斥量, N值表示可以同时访问数据区的线程数
    • 可以用于线程间和进程间的同步

    主要函数

    int sem_init(sem_t *sem, int pshared, unsigned int value);
    int sem_destroy(sem_t *sem);
    int sem_wait(sem_t *sem);
    int sem_trywait(sem_t *sem);
    int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict abs_timeout);
    int sem_post(sem_t *sem);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    信号的基本操作

    sem_wait:信号量大于0, 则信号量--					(pthread_mutex_lock)
    		  信号量等于0, 造成线程阻塞
    					
    sem_post:信号量++, 同时唤醒阻塞在信号量上的线程		(pthread_mytex_unlock)
    			信号量为N时, 再次++就会阻塞
    
    • 1
    • 2
    • 3
    • 4
    • 5

    信号量的初值,决定了占用信号量的线程的个数

    sem_init函数

    作用:初始化信号量
    int sem_init(sem_t *sem, int pshared, unsigned int value);
    参数:
    	sem: 信号量
    	pshared:
    		0: 线程间同步
    		1: 进程间同步
    	value: N值, 指定同时访问的线程数
    返回值:
    	成功: 0
    	失败: -1, 设置errno
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    sem_timedwait函数

    int sem_timedwait(sem_t *restrict sem, const struct timespec *restrict abs_timeout);
    参数:
    	sem: 等待的信号量,从1970.1.1开始到现在的时间
    	abs_timeout: 绝对时间
    
    
    struct timespec {
        time_t tv_sec;      /* Seconds */
        long   tv_nsec;     /* Nanoseconds [0 .. 999999999] */
    };
    
    定时1s:
    time_t cur  = time(NULL);		// 获取当前的时间
    struct timespec t;				// 定义timespec结构体变量t
    t.tv_sec = cur + 1;				// 定时1s
    t.tv_nsec = t.tv_sec + 100;		
    sem_timedwaoit(&sem, &t);		// 传参
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    用信号量来实现生产者和消费者模型

    #include <stdlib.h>
    #include <unistd.h>
    #include <pthread.h>
    #include <stdio.h>
    #include <semaphore.h>
    
    #define NUM 5
    
    int queue[NUM];                         // 全局数组实现环形队列
    sem_t blank_number, product_number;     // 空格子信号量, 产品信号量
    
    void *producer(void *arg) {
        int i = 0;
    
        while (1) {
            sem_wait(&blank_number);        // 生产者将格子数--
            queue[i] = rand() % 1000 + 1;   // 生产一个产品
            printf("----Produce---%d\n", queue[i]);
            sem_post(&product_number);      // 产品数++
    
            i = (i + 1) % NUM;
            sleep(rand() % 1);
        }
    }
    
    void *consumer(void * arg) {
        int i = 0;
        while (1) {
            sem_wait(&product_number);          // 产品数--
            printf("-Consume---%d\n", queue[i]);
            queue[i] = 0;
            sem_post(&blank_number);            // 格子数++
    
            i = (i + 1) % NUM;
            sleep(rand() % 3);
        }
    }
    
    int main() {
        pthread_t pid, cid;
        sem_init(&blank_number, 0, NUM);
        sem_init(&product_number, 0, 0);
    
        pthread_create(&pid, NULL, producer, NULL);
        pthread_create(&cid, NULL, consumer, NULL);
        
        pthread_join(pid, NULL);
        pthread_join(cid, NULL);
    
        sem_destroy(&blank_number);
        sem_destroy(&product_number);
    
        return 0;
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
  • 相关阅读:
    Easyx图片操作
    elasticsearch api 调用
    MYSQL数据库管理-binlog二进制日志管理与维护
    模型的保存加载、模型微调、GPU使用及Pytorch常见报错
    初识Pyqt5
    ​辉瑞校园《乡村振兴战略下传统村落文化旅游设计》许少辉八一新著作——2023学生开学季辉少许
    详解127.0.0.1和localhost的关系和区别
    C语言实现三子棋小游戏(控制台版本)
    叶酸FA修饰CdS/ZnS硫化镉/硫化锌量子点|氨基NH2修饰CdTe/ZnS碲化镉/硫化锌量子点
    QCC51XX---QACT用户指南
  • 原文地址:https://blog.csdn.net/ghost_him/article/details/125623162