线程同步
数据混乱的原因:
- 资源共享
- 调度随机
- 线程间缺乏必要的同步机制(由cpu分配)
锁的使用:
建议锁!对公共数据进行保护。 所有线程应该在访问公共数据前先拿锁再访问。但是锁不具备强制性。
互斥锁
使用mutex(互斥量,互斥锁)的一般步骤
pthread_mutex lock
创建锁pthread_mutex_init
初始化pthread_mutex_lock
加锁- 访问共享数据
pthread_mutex_unlock
解锁pthread_mutex_destory
销毁锁
使用锁的注意事项
- 尽量保证锁的粒度,越小越好,即使用共享数据前加锁, 访问结束以后立即解锁
- 互斥锁的本质为结构体, 可以想象为整形, 初值为 1(即
pthread_init
函数调用成功)- 加锁: --操作
- 解锁: ++操作
- try锁: 尝试加锁, 成功则–, 失败则返回错误号
EBUSY
初始化互斥变量:
pthread_mutex_init()
动态初始化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 // 一次通知一堆阻塞在条件变量上的线程
初始化条件变量:
pthread_cond_init()
动态初始化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);
生产者,消费者模型
公共步骤:
- 创建锁
pthread_mutex_t mutex
- 初始化
pthread_mutex_init()
- 加锁
pthread_mutex_lock
消费者 | 生产者 |
---|---|
1. 等待条件满足pthread_cond_wait() | 1. 生产数据 |
2. 访问共享数据 | 2. 加锁 pthread_mutex_lock() |
3.循环 | 3. 将数据放到公共区域 |
4. 解锁pthread_mutex_unlock() | |
5.通知阻塞在条件上的线程 | |
6.循环 |
- 解锁,释放条件变量, 释放锁
生产者,消费者模型代码
#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;
}