一、引入
例:
要求定义一个全局变量 char buf[] = "1234567",创建两个线程,不考虑退出条件。
A线程循环打印buf字符串,
B线程循环倒置buf字符串,即buf中本来存储1234567,倒置后buf中存储7654321. 不打印!!
倒置不允许使用辅助数组。
要求A线程打印出来的结果只能为 1234567 或者 7654321 不允许出现7634521 7234567
不允许使用sleep函数
#include <head.h>
char buf[] = "1234567";
void *callback1(void *arg)
{
while (1)
{
printf("%s\n", buf);
}
pthread_exit(NULL);
}
void *callback2(void *arg)
{
char temp = 0;
while (1)
{
for (int i = 0; i < strlen(buf) / 2; i++)
{
temp = buf[i];
buf[i] = buf[strlen(buf) - i - 1];
buf[strlen(buf) - i - 1] = temp;
}
}
}
int main(int argc, char const *argv[])
{
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, callback1, NULL) != 0)
{
printf("创建失败\n");
return -1;
}
// pthread_detach(tid1);
if (pthread_create(&tid2, NULL, callback2, NULL) != 0)
{
printf("创建失败\n");
return -1;
}
// pthread_detach(tid2);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
很明显输出的字符不符合规范,所以我们要加入一个标志位flag
#include <head.h>
char buf[] = "1234567";
int flag = 0;
void *callback1(void *arg)
{
while (1)
{
if (flag == 0)
{
printf("%s\n", buf);
flag = 1;
}
}
pthread_exit(NULL);
}
void *callback2(void *arg)
{
char temp = 0;
while (1)
{
if (flag == 1)
{
for (int i = 0; i < strlen(buf) / 2; i++)
{
temp = buf[i];
buf[i] = buf[strlen(buf) - i - 1];
buf[strlen(buf) - i - 1] = temp;
}
flag = 0;
}
}
}
int main(int argc, char const *argv[])
{
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, callback1, NULL) != 0)
{
printf("创建失败\n");
return -1;
}
// pthread_detach(tid1);
if (pthread_create(&tid2, NULL, callback2, NULL) != 0)
{
printf("创建失败\n");
return -1;
}
// pthread_detach(tid2);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
二、线程同步互斥的概念
互斥:指某个资源同一时间只允许一个访问者对其进行访问,具有唯一性。互斥机制无法限制访问者的访问顺序,即访问者的访问顺序是无序的,如互斥锁
同步:在互斥的基础上,使用别的手段实现访问者的有序访问,如条件变量
三、线程同步互斥的实现
线程之间如果要进行通信,需要引入同步互斥机制,避免产生竞态,保证在同一时刻,只有一个线程在处理临界资源。
由于flag的效率太低,当flag的条件不满足时,程序会在while循环内白白浪费时间,所以我们引入以下同步互斥机制。
2.1互斥锁
原理:
1.访问临界资源之前要先申请互斥锁
2.申请上锁,进入临界区访问临界资源,退出临界区,解开锁
3.如果申请上锁失败,说明互斥锁被别的线程占用,该线程进入休眠,等待互斥锁解开
4.互斥锁只能保证临界区完整,只有一个线程访问,但无法制定访问者的顺序
#include <head.h>
char buf[] = "1234567";
// 互斥锁
pthread_mutex_t mutex; // 放在全局可以让所有函数访问到
void *callback1(void *arg)
{
while (1)
{
// 上锁
pthread_mutex_lock(&mutex);
printf("%s\n", buf);
// 解锁
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
void *callback2(void *arg)
{
char temp = 0;
while (1)
{
// 上锁
pthread_mutex_lock(&mutex);
for (int i = 0; i < strlen(buf) / 2; i++)
{
temp = buf[i];
buf[i] = buf[strlen(buf) - i - 1];
buf[strlen(buf) - i - 1] = temp;
}
// 解锁
pthread_mutex_unlock(&mutex);
}
}
int main(int argc, char const *argv[])
{
// 申请一个互斥锁
pthread_mutex_init(&mutex, NULL);//null代表互斥锁属性为默认
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, callback1, NULL) != 0)
{
printf("创建失败\n");
return -1;
}
if (pthread_create(&tid2, NULL, callback2, NULL) != 0)
{
printf("创建失败\n");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
// 销毁锁
pthread_mutex_destroy(&mutex);
return 0;
}
死锁:
拥有锁资源的任务没有释放锁
1.持有互斥锁的线程异常退出,没有释放锁资源
2.同一线程对一把互斥锁重复上锁
3.互斥锁交叉嵌套
2.2信号量(信号灯)
原理:
1.线程要访问临界资源都要去申请信号量
当信号量的值>0时,申请成功,信号量-1
当信号量的值=0时,申请失败,该申请操作会阻塞,线程休眠,等待信号量>0
2.互斥锁又被称之为二值信号量,只允许一个线程进入临界区,即信号量的初始值为1
3.信号量根据初始值不同,可以让一个或多个线程同时进入临界区。
4.PV操作:
p操作:申请信号量,信号量-1
v操作:释放信号量,信号量+1
#include <head.h>
// 信号量
sem_t sem;
void *callback(void *arg)
{
sleep(3);
// V操作
if (sem_post(&sem) < 0)
{
perror("sem_post\n");
return NULL;
}
printf("v操作\n");
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
if (sem_init(&sem, 0, 2) < 0)
// 第二个参数:共享标识,0表示用于线程,非0表示用于进程
// 第三个参数:指定信号量的初始值
{
perror("sem_init");
return -1;
}
// 创建一个线程,3s后V一次
pthread_t tid;
if (pthread_create(&tid, NULL, callback, NULL) != 0)
{
printf("创建失败\n");
return -1;
}
// p操作
if (sem_wait(&sem) < 0)
{
perror("sem_wait");
return -1;
}
printf("p操作\n");
// p操作
if (sem_wait(&sem) < 0)
{
perror("sem_wait");
return -1;
}
printf("p操作\n");
// p操作
if (sem_wait(&sem) < 0)
{
perror("sem_wait");
return -1;
}
printf("p操作\n");
pthread_join(tid, NULL);
sem_destroy(&sem);
return 0;
}
2.3条件变量(条件变量需要与互斥锁共用)
原理:
将不访问共享资源的线程直接休眠,并设置一个唤醒条件,当线程需要访问的时候,其他线程通过制定的条件变量唤醒该线程。
#include <head.h>
// 互斥锁
pthread_mutex_t mutex;
// 条件变量
pthread_cond_t cond;
int flag = 0;
void *callback1(void *arg)
{
while (1)
{
pthread_mutex_lock(&mutex);
if (flag != 0)
{
pthread_cond_wait(&cond, &mutex);
// 设置唤醒条件同时解开互斥锁
// 休眠,等待被唤醒
}
printf("A\n");
flag = 1;
pthread_cond_signal(&cond); // 通过条件变量唤醒线程
// 只需记住调用signal肯定会随机唤醒一个睡在cond上的线程
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
void *callback2(void *arg)
{
while (1)
{
pthread_mutex_lock(&mutex);
if (flag != 1)
{
pthread_cond_wait(&cond, &mutex);
}
printf("B\n");
flag = 0;
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
// 创建互斥锁
pthread_mutex_init(&mutex, NULL);
// 创建条件变量
if (pthread_cond_init(&cond, NULL) != 0)
{
printf("创建失败");
return -1;
}
// 创建分支线程
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, callback1, NULL) != 0)
{
printf("创建失败");
return -1;
}
if (pthread_create(&tid2, NULL, callback2, NULL) != 0)
{
printf("创建失败");
return -1;
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_cond_destroy(&cond);
pthread_mutex_destroy(&mutex);
return 0;
}
练习
例:每次生产3个苹果,线程1每次消耗4个苹果,线程2每次消耗7个苹果
#include <head.h>
int apple = 10;
pthread_mutex_t mutex;
pthread_cond_t cond;
void *callback1(void *arg)
{
while (1)
{
pthread_mutex_lock(&mutex);
if (apple < 4)
{
pthread_cond_wait(&cond, &mutex);
}
apple -= 4;
printf("1#线程消费了4个苹果,现在苹果数量为:%d\n", apple);
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
void *callback2(void *arg)
{
while (1)
{
pthread_mutex_lock(&mutex);
if (apple < 7)
{
pthread_cond_wait(&cond, &mutex);
}
apple -= 7;
printf("2#线程消费了7个苹果,现在苹果数量为:%d\n", apple);
pthread_mutex_unlock(&mutex);
}
pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_t tid1, tid2;
if (pthread_create(&tid1, NULL, callback1, NULL) != 0)
{
printf("创建失败\n");
return -1;
}
if (pthread_create(&tid2, NULL, callback2, NULL) != 0)
{
printf("创建失败\n");
return -1;
}
while (1)
{
apple += 3;
if (apple >= 7)
{
pthread_cond_signal(&cond);
}
printf("生产了3个苹果,现在苹果数量为:%d\n", apple);
sleep(1);
}
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}