多线程,线程安全


多线程

线程安全

线程安全:多个执行流对临界资源争抢访问,但是不会出现数据二义性

线程安全的实现

 同步:通过条件判断保证对临界资源访问的合理性
 互斥:通过同一时间对临界资源访问的唯一性实现临界资源访问的安全性

同步是如何实现/互斥又是如何实现?
 互斥的实现:互斥锁
  互斥锁实现互斥原理:互斥锁本身是一个只有0/1的计数器,描述了一个临界资源当前的访问状态,所有执行流在访问临界资源资源都需要判断当前的临界资源状态是否允许访问,如果不允许则让执行流等待,否则可以让执行流访问临界资源,但是在访问期间需要将状态修改为不可访问状态,这期间如果由其它执行流想要访问,则不被允许。

互斥

互斥锁具体的操作流程以及接口介绍:

1.定义互斥锁变量
pthread_mutex_t mutex;
2.初始化互斥锁变量
pthread_mutex_init(pthread_mutex* mutex, pthread_mutexattr_t* arr)使用接口初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;使用宏初始化
3.在访问临界资源之前进行加锁操作(不能加锁则等待,可以加锁则修改资源状态,然后调用返回,访问临界资源)
pthread_mutex_lock(pthread_mutex* mutex)
阻塞加锁----如果当前不能加锁(锁已经被别人加了),则一直等待直到加锁成功调用返回
pthread_mutex_trylock(pthread_mutex* mutex)
非阻塞加锁----能加锁就加锁,不能加锁就报错返回
4.在临界资源访问完毕之后进行解锁操作(将资源状态置为可访问,将其他执行流唤醒)
pthread_mutex_unlock(pthread_mutex_t* mutex)
5.销毁互斥锁
pthread_mutex_destroy(pthread_mutex_t* mutex)

eg:

/*以黄牛党抢票为例*/
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>

int ticket = 100;
pthread_mutex_t mutex;
void* thr_scalpers(void* arg){
    while(1){
        pthread_mutex_lock(&mutex);
        if(ticket>0){
            //有票就一直抢      
            usleep(1000);
            printf("I got a ticket: %d\n", ticket);
            ticket--;
            pthread_mutex_unlock(&mutex);
        }
        else{
            pthread_mutex_unlock(&mutex);
            pthread_exit(NULL);
        }
    }
    return NULL;
}
int main(){
    pthread_t tid[4];
    int i;
    pthread_mutex_init(&mutex, NULL);          // 互斥锁的初始化;
    for(i = 0; i < 4; i++){
        int ret = pthread_create(&tid[i], NULL, thr_scalpers, NULL);
        if(ret != 0){
            printf("thread create error");
        }
    }
    for(i = 0; i < 4; i++){
        pthread_join(tid[i], NULL);
    }
    pthread_mutex_destroy(&mutex);
    return 0;
}

所有的执行流都需要通过同一个互斥锁实现,意味互斥锁本身就是一个临界资源,大家都访问。如果互斥锁本身的操作都不安全如何保证别人安全,所以互斥锁本身的操作首先必须是安全的;----互斥锁自身的操作是原子操作

在这里插入图片描述
不管当前mutex的状态是什么,反正一部交换之后,其他线程都是不可访问的;这时候当前线程就可以慢慢判断了

具体:
1.先将寄存器的值置为0
2.直接加你个寄存器的值与内存空间中的数据进行交换----这个交换指令是一步可以完成的(这时候内存中的mutex的值就是0了,别人访问肯定发现无法加锁)
3.if(mutex == 1){
则pthread_mutex_lock直接返回,访问临界资源
}
else{
则让线程等待
}

死锁

多个执行流对锁资源进行争抢访问,但是因为访问推进顺序不当,造成互相等到程序流程无法继续推进,这时候就造成了死锁。
 死锁实际是一种程序流程无法继续推荐,卡在某个位置的一种概念,死锁的产生通常是在访问多个锁的时候需要注意的事项。

死锁产生的必要条件:(有一条不满足就不会产生死锁)
1.互斥条件:一个资源同一时间只有一个人能够访问,我加了锁,别人就不能继续加锁。
2.不可剥夺条件:我加的锁别人不能解,只有我能解锁
3.请求与保持条件:我加了
A锁,然后去请求B锁;如果不能对 B锁加锁,则也不释放A锁。
4.环路等待条件:我加了A锁,然后去请求B锁;另一个人加了B锁,然后请求A锁

死锁预防:破坏死锁产生的必要条件(主要避免3和4两个条件的产生)

死锁的避免:死锁检测算法/银行家算法

银行家算法思路: 系统的安全状态/非安全状态
一张表记录当前有哪些锁,一张表记录已经给谁分配了哪些锁,一张表记录谁当前需要哪些锁。按照三张表进行判断,判断若给一个执行流分配了指定的锁,是否会达成环路等待条件导致系统的运行进入不安全状态,如果有可能就不能分配,反之,若分配了之后不会造成环路等待,系统是安全的,则分配这个锁。若不能分配锁,可以资源回溯,把当前执行流已经加的锁释放掉。

同步

同步的实现

线程在满足资源的访问条件的时候才能去访问资源,否则就挂起线程;直到满足条件之后再唤醒线程;----条件变量
 条件变量:向外提供了一个使线程等待的接口和唤醒线程的接口 +
pcb等待队列
  因为条件变量只提供了使线程等待和唤醒的接口,因此什么时候让线程等待/唤醒就需要程序员在进程中进行判断(访问条件是否满足的判断由我们自己完成)

操作流程

1.定义条件变量
pthread_cond_t
2.初始化条件变量
pthread_cond_init(pthread_cond_t*,pthread_condattr_t*)
PTHREAD_COND_INITIALIZER
3.使线程挂起休眠的接口:条件变量需要搭配互斥锁一起使用
pthread_cond_wait(pthread_cond_t*, pthread_mutex_t*)----一直等待别人的唤醒
pthread_cond_timewait(pthread_cond_t*, pthread_mutex_t*, struct timespec)----等待指定时间内都没有被唤醒则自动醒来
4.唤醒线程的接口
pthread_cond_signal(pthread_cond_t*)----唤醒至少一个等待线程
pthread_cond_broadcast(pthread_cond_t*)----唤醒所有等待线程
5.销毁条件变量
pthread_cond_destroy(pthread_cond_t*)
例:单消费者 和 生产者 模型
在这里插入图片描述

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>

int bowl = 0; //默认0表示碗中没有饭

pthread_cond_t cond;       //实现线程间对bowl变量访问的同步操作
pthread_mutex_t mutex;     //保护bowl变量的访问操作
void* thr_cook(void* arg){
    while(1){
        pthread_mutex_lock(&mutex);
        if(bowl != 0){   //表示有饭,不满足做饭的条件
            //让厨师线程等待,等待之前先解锁,被唤醒之后再加锁
            //pthread_cond_wait接口中就完成了解锁,休眠,被唤醒后加锁三部分
            //并且解锁和休眠操作时一步完成,保证原子操作  
            pthread_cond_wait(&cond, &mutex);
        }
        bowl = 1;  //能够走下来表示没饭,bowl == 0,则做一碗饭,将bowl修改为1
        printf("i made a bowl of rice\n");
        //唤醒顾客吃饭
        pthread_cond_signal(&cond);
        //解锁
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
void* thr_customer(void* arg){
    while(1){
        pthread_mutex_lock(&mutex); //加锁
        if(bowl != 1){      //没有饭,不满足吃饭条件,则等待
            //没有饭则等待,等待前先解锁,被唤醒后加锁
            pthread_cond_wait(&cond, &mutex);
        }
        bowl = 0; //能够走下来表示有饭,bowl == 1;吃完饭,将bowl修改为0
        printf("i had a bowl of rice\n");
        //唤醒厨师做饭
        pthread_cond_signal(&cond);
        //解锁  
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
int main(){
    pthread_t cook_tid, customer_tid;
    int ret;
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond, NULL);
    ret = pthread_create(&cook_tid, NULL, thr_cook, NULL);
    if(ret != 0){
        printf("pthread_create error\n");
        return -1;
    }
    ret = pthread_create(&customer_tid, NULL, thr_customer, NULL);
    if(ret != 0){
        printf("pthread_create error\n");
        return -1;
    }
    pthread_join(cook_tid, NULL);
    pthread_join(customer_tid, NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);
    return 0;
}

多个顾客线程,因为没有饭而等待,一个厨师做好饭,调用pthread_cond_signal唤醒顾客线程,然而pthread_cond_signal唤醒至少一个线程,假设三个顾客线程都被唤醒其中只有一个顾客加锁成功去吃饭,其他两个顾客线程卡在加锁这里,加锁成功的顾客吃饭,唤醒厨师进行解锁(时间片调度到谁,谁运行),这时候加锁成功的不一定是厨师,有可能是卡在被唤醒后加锁的顾客线程----则这个线程没有饭的情况下吃了一碗----逻辑错误。这种逻辑错误的避免:条件的判断应该是一个循环判断,顾客线程被唤醒加锁成功后重新判断有没有饭,没有就休眠,有则吃饭。

条件变量只有一个,意味着等待队列只有一个,顾客没饭吃就要挂到等待队列上,厨师不能做饭也要挂在等待队列上,三个厨师在有饭的情况下,就会挂在等待队列上。假设一个顾客线程吃完饭,要唤醒厨师,唤醒一个厨师后,因为没有饭,重新挂到队列上。

解决方案:不同角色的线程应该挂在不同线程应该挂在不同的等待队列上进行等待,唤醒的时候,就比较有目的性的唤醒。
例: 多消费者 和生产者 模型

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<pthread.h>

int bowl = 0; //默认0表示碗中没有饭

pthread_cond_t cook_cond;       //实现线程间对bowl变量访问的同步操作
pthread_cond_t customer_cond;
pthread_mutex_t mutex;     //保护bowl变量的访问操作
void* thr_cook(void* arg){
    while(1){
        pthread_mutex_lock(&mutex);
        while(bowl != 0){   //表示有饭,不满足做饭的条件
            //让厨师线程等待,等待之前先解锁,被唤醒之后再加锁
            //pthread_cond_wait接口中就完成了解锁,休眠,被唤醒后加锁三部分
            //并且解锁和休眠操作时一步完成,保证原子操作  
            pthread_cond_wait(&cook_cond, &mutex);
        }
        bowl = 1;  //能够走下来表示没饭,bowl == 0,则做一碗饭,将bowl修改为1
        printf("i made a bowl of rice\n");
        //唤醒顾客吃饭
        pthread_cond_signal(&customer_cond);
        //解锁
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}
void* thr_customer(void* arg){
    while(1){
        pthread_mutex_lock(&mutex); //加锁
        while(bowl != 1){       //没有饭,不满足吃饭条件,则等待
            //没有饭则等待,等待前先解锁,被唤醒后加锁
            pthread_cond_wait(&customer_cond, &mutex);
        }
        bowl = 0; //能够走下来表示有饭,bowl == 1;吃完饭,将bowl修改为0
        printf("i had a bowl of rice\n");
        //唤醒厨师做饭
        pthread_cond_signal(&cook_cond);
        //解锁  
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
int main(){
    pthread_t cook_tid[4], customer_tid[4];
    int ret;
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cook_cond, NULL);
    pthread_cond_init(&customer_cond, NULL);
    int i = 0;
    for(i = 0; i < 4; i++){
        ret = pthread_create(&cook_tid[i], NULL, thr_cook, NULL);
        if(ret != 0){
            printf("pthread_create error\n");
            return -1;
        }   
    }   
    for(i = 0; i < 4; i++){
        ret = pthread_create(&customer_tid[i], NULL, thr_customer, NULL);
        if(ret != 0){
            printf("pthread_create error\n");
            return -1;
        }   
    }   
    pthread_join(cook_tid[0], NULL);
    pthread_join(customer_tid[0], NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cook_cond);
    pthread_cond_destroy(&customer_cond);
    return 0;
}   

1.条件变量使用中对条件的判断应该使用while循环
2.多个角色线程应该使用多个条件变量

生产者消费者模型

大佬们针对典型的应用场景设计的解决方案

生产者与消费者模型应用场景:有线程不断生产数据,有线程不断处理数据
数据的生产与数据的处理,放在同一线程中完成,因为执行流只有,那么肯定是生产一个处理一个,处理完一个后生产一个。这样依赖关系太强----如果处理比较慢,也会拖的生产速度慢下来。因此将生产与处理放到不同执行流中完成,中间增加一个数据缓冲区,做为中间的数据缓冲场所。
在这里插入图片描述
生产者与消费者模型解决的问题:1.解耦合 / 2.支持忙闲不均 / 3.支持并发
并发:轮询处理
并行:同时处理
而这里的支持并发:指的是可以有多个执行流进行处理

生产者与消费者,其实是两种业务处理的线程而已----我们创建线程就可以
实现的关键在于线程安全的队列

#define QUEUE_MAX 5;

class BlockQueue{   //线程安全阻塞队列---没有数据则消费者阻塞/数据满了则生产者阻塞
public:
	BlockQueue(int maxq = QUEUE_MAX):_capacity(maxq){
		pthread_mutex_init(&mutex, NULL);
		pthread_cond_init(&_productor_cond, NULL);
		pthread_cond_init(&_customer_cond, NULL);
	}
	~BlockQueue(){
		pthread_mutex_destroy(&mutex, NULL);
		pthread_cond_destroy(&_productor_cond, NULL);
		pthread_cond_destroy(&_customer_cond, NULL);
	}
	bool push(const element& data){		//入队数据
		//只有生产者才会入队数据,如果队列中数据满了则需要阻塞
		pthread_mutex_lock(&mutex);
		while(_queue.size() == _capacity){
			pthread_cond_wait(&productor_cond, &mutex);
		}
		_queue.push(data); //queue.push()入队操作
		_pthread_mutex_unlock(&mutex);
		_pthread_cond_signal(&_customer_cond);
		return true;
	}
	bool pop(element* data){	//出队数据
		//只有消费者才会出队数据,如果队列中数据没有则需要阻塞
		pthread_mutex_lock(&mutex);
		while(_queue.size() == 0){
			pthread_cond_wait(&customer_cond, &mutex);
		}
		*data = _queue.front();
		_queue.pop(); //queue.push()入队操作
		_pthread_mutex_unlock(&mutex);
		_pthread_cond_signal(&_productor_cond);
		return true;
	}
	}
private:
	queue<int> _queue;
	int _capacity;	//队列中节点最大数量
	pthread_mutex_t _mutex;
	pthread_cond_t _productor_cond;  //生产者队列
	pthread_cond_t _customer_cond;	 //消费者队列
};

void* thr_productor(void* arg){
	BlockQueue* queue = (BlockQueue*) arg;
	int i = 0;
	while(1){
		//生产者不断生产数据
		queue->push(i);
		printf("productor push data: %d\n", i++);
	}
	return NULL;
}
void* thr_customer(void* arg){
	BlockQueue* queue = (BlockQueue*) arg;
	while(1){
		//消费者不断消耗数据
		int data;
		queue->pop(&data);
		printf("customer pop data: %d\n", data);
	}
	return NULL;
}

int main(){
	int ret, i;
	pthread_t ptid[4], ctid[4];
	BlockQueue queue;
	for(i = 0; i < 4; i++){
		ret = pthread_create(&ptid[i], NULL, thr_productor, (void*)&queue);
		if(ret == 0){
			printf("create productor pthread error");
			return -1;
		}
		ret = pthread_create(&ctid[i], NULL, thr_customer, (void*)&queue);
		if(ret == 0){
			printf("create customer pthread error");
			return -1;
		}
	}
	for(i = 0; i < 4; i++){
		pthread_join(ptid[i], NULL);
		pthread_join(ctid[i], NULL);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
基于YOLOv9实现工业布匹缺陷(破洞、污渍)检测系统python源码+详细运行教程+训练好的模型+评估 【使用教程】 一、环境配置 1、建议下载anaconda和pycharm 在anaconda中配置好环境,然后直接导入到pycharm中,在pycharm中运行项目 anaconda和pycharm安装及环境配置参考网上博客,有很多博主介绍 2、在anacodna中安装requirements.txt中的软件包 命令为:pip install -r requirements.txt 或者改成清华源后再执行以上命令,这样安装要快一些 软件包都安装成功后才算成功 3、安装好软件包后,把anaconda中对应的python导入到pycharm中即可(不难,参考网上博客) 二、环境配置好后,开始训练(也可以训练自己数据集) 1、数据集准备 需要准备yolo格式的目标检测数据集,如果不清楚yolo数据集格式,或者有其他数据训练需求,请看博主yolo格式各种数据集集合链接:https://blog.csdn.net/DeepLearning_/article/details/127276492 里面涵盖了上百种yolo数据集,且在不断更新,基本都是实际项目使用。来自于网上收集、实际场景采集制作等,自己使用labelimg标注工具标注的。数据集质量绝对有保证! 本项目所使用的数据集,见csdn该资源下载页面中的介绍栏,里面有对应的下载链接,下载后可直接使用。 2、数据准备好,开始修改配置文件 参考代码中data文件夹下的banana_ripe.yaml,可以自己新建一个不同名称的yaml文件 train:训练集的图片路径 val:验证集的图片路径 names: 0: very-ripe 类别1 1: immature 类别2 2: mid-ripe 类别3 格式按照banana_ripe.yaml照葫芦画瓢就行,不需要过多参考网上的 3、修改train_dual.py中的配置参数,开始训练模型 方式一: 修改点: a.--weights参数,填入'yolov9-s.pt',博主训练的是yolov9-s,根据自己需求可自定义 b.--cfg参数,填入 models/detect/yolov9-c.yaml c.--data参数,填入data/banana_ripe.yaml,可自定义自己的yaml路径 d.--hyp参数,填入hyp.scratch-high.yaml e.--epochs参数,填入100或者200都行,根据自己的数据集可改 f.--batch-size参数,根据自己的电脑性能(显存大小)自定义修改 g.--device参数,一张显卡的话,就填0。没显卡,使用cpu训练,就填cpu h.--close-mosaic参数,填入15 以上修改好,直接pycharm中运行train_dual.py开始训练 方式二: 命令行方式,在pycharm中的终端窗口输入如下命令,可根据自己情况修改参数 官方示例:python train_dual.py --workers 8 --device 0 --batch 16 --data data/coco.yaml --img 640 --cfg models/detect/yolov9-c.yaml --weights '' --name yolov9-c --hyp hyp.scratch-high.yaml --min-items 0 --epochs 500 --close-mosaic 15 训练完会在runs/train文件下生成对应的训练文件及模型,后续测试可以拿来用。 三、测试 1、训练完,测试 修改detect_dual.py中的参数 --weights,改成上面训练得到的best.pt对应的路径 --source,需要测试的数据图片存放的位置,代码中的test_imgs --conf-thres,置信度阈值,自定义修改 --iou-thres,iou阈值,自定义修改 其他默认即可 pycharm中运行detect_dual.py 在runs/detect文件夹下存放检测结果图片或者视频 【特别说明】 *项目内容完全原创,请勿对项目进行外传,或者进行违法等商业行为! 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通交流。 2、适用人群:计算机相关专业(如计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等)在校学生、专业老师或者企业员工下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值