多线程共享同一进程的地址空间。
优点: 线程之间通讯 就变得很容易
通过全局变量实现数据的共享和交换
缺点: 多个线程同时访问共享对象时需要引入同步和互斥的概念!!!
互斥: 同一时间 只能有一个任务(进程或线程)执行,谁先执行不确定!!
同步: 同一时间 只能有一个任务(进程或线程)执行,有顺序的执行!!
同步是 特殊的互斥!!!!
一、全局变量资源共享
#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;
}