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
	失败: 错误值

restrict的作用:

  • 用来修饰指针
  • 表示当前指针指向的那个地址空间只能由当前的指针能操作, 其他的指针不能操作
pthread_mutex_destroy函数
作用: 销毁一个互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
参数:
	mutex: 待销毁的锁
返回值:
	成功: 0
	失败: 错误值
pthread_mutex_lock函数
作用: 给指定的锁上锁, 若锁已经被上, 则阻塞线程
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:
	mutex: 待上锁的锁
返回值:
	成功: 0
	失败: 错误值
pthread_mutex_unlock函数
作用: 给指定的锁解锁, 同时唤醒阻塞在锁上的线程
int pthread_mutex_lock(pthread_mutex_t *mutex);
参数:
	mutex: 待解锁的锁
返回值:
	成功: 0
	失败: 错误值
pthread_mutex_trylock函数
作用: 尝试给指定的锁加锁(不阻塞)
int pthread_mutex_trylock(pthread_mutex_t *mutex);
参数:
	mutex: 待加锁的锁
返回值:
	成功上锁: 0
	该锁已经被上: 错误号(EBUSY)
死锁
  • 是使用锁不恰当锁导致的一种现象
    • 对一个锁反复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);

用法和mutex函数类似

条件变量

  • 本身不是锁的一种, 通常要结合互斥锁来使用
主要函数
pthread_cond_init
pthread_cond_destroy
pthread_cond_wait			// 等待某一个条件满足
pthread_cond_timedwait		// 等待某一个条件满足, 并设置等待的时间
pthread_cond_signal			// 一次通知至少一个阻塞在条件变量上的线程
pthread_cond_broadcast		// 一次通知一堆阻塞在条件变量上的线程

初始化条件变量:

  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);
pthread_cond_destroy函数
int pthread_cond_destroy(pthread_cond_t *cond);

用法与互斥锁基本一直


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. 创建锁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;
}

信号量

  • 相当于初始化值为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);
信号的基本操作
sem_wait:信号量大于0, 则信号量--					(pthread_mutex_lock)
		  信号量等于0, 造成线程阻塞
					
sem_post:信号量++, 同时唤醒阻塞在信号量上的线程		(pthread_mytex_unlock)
			信号量为N时, 再次++就会阻塞

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

sem_init函数
作用:初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:
	sem: 信号量
	pshared:
		0: 线程间同步
		1: 进程间同步
	value: N值, 指定同时访问的线程数
返回值:
	成功: 0
	失败: -1, 设置errno
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);		// 传参

用信号量来实现生产者和消费者模型
#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;
    
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值