Linux系统程序设计--6.线程

线程基本概念

在这里插入图片描述
在这里插入图片描述

  • 刚创建的进程默认有一个线程,成为主控线程(主线程)

在这里插入图片描述

Linux线程实现

在这里插入图片描述

线程标识

在这里插入图片描述

Linux线程的创建和终止

pthread_create

在这里插入图片描述

龟兔赛跑案例

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<math.h>void * th_fn(void *arg)
{int distance = (int)arg;int i ;for(i = 1;i<=distance;i++){printf("%lx run %d\n",pthread_self(),i);//int time = (int)(drand48()*100000);sleep(2); //微妙}return (void*)0;
}int main()
{int err;pthread_t  rabbit,turtle; // 定义线程标识符// 创建rabbit线程if((err = pthread_create(&rabbit,NULL,th_fn,(void*)50))!=0){perror("pthread_create error");}// 创建turtle线程if((err = pthread_create(&turtle,NULL,th_fn,(void*)50))!=0){perror("pthread_create error");}printf("control thread id:%lx\n",pthread_self());printf("finishe\n");sleep(10);  // 让主控线程先睡眠一下return 0;
}

在这里插入图片描述

参数arg


#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<math.h>typedef struct
{char name[20];int time;int start;int end;
}RaceArg;void * th_fn(void *arg)
{RaceArg *r = (RaceArg*)arg;for(int i = r->start;i<=r->end;i++){printf("%s %lx run %d\n",r->name,pthread_self(),i);usleep(r->time);}return (void*)0;
}int main()
{int err;pthread_t  rabbit,turtle; // 定义线程标识符RaceArg r_a = {"rabbit",(int)(drand48()*1000000),20,50};RaceArg t_a = {"turtle",(int)(drand48()*1000000),10,60};// 创建rabbit线程if((err = pthread_create(&rabbit,NULL,th_fn,(void*)&r_a))!=0){perror("pthread_create error");}// 创建turtle线程if((err = pthread_create(&turtle,NULL,th_fn,(void*)&t_a))!=0){perror("pthread_create error");}pthread_join(rabbit,NULL);pthread_join(turtle,NULL);//sleep(10);  // 让主控线程先睡眠一下printf("control thread id:%lx\n",pthread_self());printf("finishe\n");return 0;
}
  • 效果与原先哪个相同

  • 主控线程调用pthread_join(),自己会阻塞。直到rabbit和turtle线程结束方可运行
    在这里插入图片描述

  • 局部变量是各个线程各有一个

  • 全局变量,静态变量是共享的。
    在这里插入图片描述

终止方式

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

如何在主控进程中获得线程返回的结果

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>typedef struct{int d1;int d2;}Arg;void * th_fn(void *arg){Arg *r = (Arg*)arg;return (void*)(r->d1+r->d2);}int main(void){int err = 0;pthread_t th;Arg r = {20,50};if((err=pthread_create(&th,NULL,th_fn,(void*)&r))!=0){perror("pthread_create error");}int* result;pthread_join(th,(void**)&result);printf("result is %d\n",(int)result);printf("result is %d\n",*result);return 0;}

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

  • 返回结构体指针
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>typedef struct{int d1;int d2;}Arg;void * th_fn(void *arg){Arg *r = (Arg*)arg;return (void*)r;}int main(void){int err = 0;pthread_t th;Arg r = {20,50};if((err=pthread_create(&th,NULL,th_fn,(void*)&r))!=0){perror("pthread_create error");}Arg *result;pthread_join(th,(void**)&result);printf("result: %d",((Arg*)result)->d1+((Arg*)result)->d2);return 0;}
  • 修改龟兔赛跑案例1
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<math.h>void * th_fn(void *arg)
{int distance = (int)arg;int i ;for(i = 1;i<=distance;i++){printf("%lx run %d\n",pthread_self(),i);//int time = (int)(drand48()*100000);sleep(2); //微妙}//return (void*)0; //pthread_exit((void*)0);return (void*)distance;
}int main()
{int err;pthread_t  rabbit,turtle; // 定义线程标识符// 创建rabbit线程if((err = pthread_create(&rabbit,NULL,th_fn,(void*)50))!=0){perror("pthread_create error");}// 创建turtle线程if((err = pthread_create(&turtle,NULL,th_fn,(void*)50))!=0){perror("pthread_create error");}int result;pthread_join(rabbit,(void*)&result);printf("rabbit's distance is %d\n",result);pthread_join(turtle,(void*)&result);printf("turtle's distance is %d\n",result);//pthread_join(rabbit,NULL);//pthread_join(turtle,NULL);//sleep(10);  // 让主控线程先睡眠一下printf("control thread id:%lx\n",pthread_self());printf("finishe\n");return 0;
}
  • 修改龟兔赛跑案例2
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<math.h>typedef struct
{char name[20];int time;int start;int end;
}RaceArg;void * th_fn(void *arg)
{RaceArg *r = (RaceArg*)arg;for(int i = r->start;i<=r->end;i++){printf("%s %lx run %d\n",r->name,pthread_self(),i);usleep(r->time);}//return (void*)0;return (void*)(r->end-r->start);
}int main()
{int err;pthread_t  rabbit,turtle; // 定义线程标识符RaceArg r_a = {"rabbit",(int)(drand48()*10000000),20,50};RaceArg t_a = {"turtle",(int)(drand48()*10000000),10,60};// 创建rabbit线程if((err = pthread_create(&rabbit,NULL,th_fn,(void*)&r_a))!=0){perror("pthread_create error");}// 创建turtle线程if((err = pthread_create(&turtle,NULL,th_fn,(void*)&t_a))!=0){perror("pthread_create error");}int result;pthread_join(rabbit,(void*)&result);printf("rabbit distance is %d\n",result);pthread_join(turtle,(void*)&result);printf("turtle  distance is %d\n",result);//pthread_join(rabbit,NULL);//pthread_join(turtle,NULL);//sleep(10);  // 让主控线程先睡眠一下printf("control thread id:%lx\n",pthread_self());//printf("finishe\n");return 0;
}

线程的清理和控制函数

在这里插入图片描述
在这里插入图片描述


#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>// 定义线程清理函数
void clean_fun(void *arg)
{char *s = (char*)arg;printf("clean_func: %s\n",s);
}void * th_fun(void *arg)
{int execute = (int)arg;pthread_cleanup_push(clean_fun,"first clean func");pthread_cleanup_push(clean_fun,"second clean func")printf("thread running %lx\n",pthread_self());pthread_cleanup_pop(execute);pthread_cleanup_pop(execute);return (void*)0;
}int main(void)
{int err;pthread_t th1,th2;if((err=pthread_create(&th1,NULL,th_fun,(void*)1))!=0){perror("pthread create error");}pthread_join(th1,NULL);printf("th1(%lx) finished\n",th1);if((err=pthread_create(&th2,NULL,th_fun,(void*)1))!=0){perror("pthread create error");}pthread_join(th2,NULL);printf("th2(%lx) finished\n",th2);return 0;
}

在这里插入图片描述

进程与线程的比较

在这里插入图片描述

线程的状态转换

在这里插入图片描述

线程属性的初始化和销毁

在这里插入图片描述

分离属性

在这里插入图片描述

  • 可以通过分离属性,在不设置pthread_join的情况下回收线程的资源
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>void out_state(pthread_attr_t *attr)
{int state;if(pthread_attr_getdetachstate(attr,&state)!=0){perror("getdetachstate error\n");}else{if(state==PTHREAD_CREATE_JOINABLE){printf("joinable state\n");}else if(state == PTHREAD_CREATE_DETACHED){printf("detached state\n");}else{printf("error statei");}}
}// 线程运行函数
void * th_fn(void *arg)
{int i;int sum = 0;for(int i =0;i<=100;i++){sum+=i;}return (void*)sum;
}int main(void)
{int err;pthread_t default_th,detach_th;//定义线程属性pthread_attr_t attr;// 对线程属性进行初始化pthread_attr_init(&attr);//输出分离属性out_state(&attr);// 去分离属性的默认值,以正常方式启动子线程if((err=pthread_create(&default_th,&attr,th_fn,(void*)0))!=0){perror("pthread create error");}int res;if((err=pthread_join(default_th,(void*)&res))!=0){perror("pthread join error");}else{printf("default retrun is %d\n",(int)res);}printf("__________________________________________________________________\n");// 设置分离属性为分离状态启动pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);out_state(&attr);// 以分离状态启动子线程if((err=pthread_create(&detach_th,&attr,th_fn,(void*)0))!=0){perror("pthread create error");}if((err=pthread_join(detach_th,(void*)&res))!=0){perror("pthread join error");fprintf(stderr,"%s\n",strerror(err));}else{printf("default retrun is %d\n",(int)res);}//销毁线程属性pthread_attr_destroy(&attr);printf("0x%lx		finished\n",pthread_self());return 0;
}

在这里插入图片描述

  • 设置了分离属性就不需要调用pthread_join了

Linux线程的互斥和同步

在这里插入图片描述

线程的互斥

在这里插入图片描述

  • account.h
#ifndef __ACCOUNT_H__
#define __ACCOUNT_H__
typedef struct{int code;double balance;
}Account;extern Account* create_account(int code,double balance);
extern void destory_account(Account *a);
extern double withdraw(Account *a,double amt);
extern double deposit(Account *a,double amt);
extern double get_balance(Account *a);#endif
  • accouont.c
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include"account.h"Account* create_account(int code,double balance)
{Account *a = (Account*)malloc(sizeof(Account));assert(a!=NULL);a->code = code;a->balance = balance;return a;
}
void destory_account(Account *a)
{if(a!=NULL){free(a);a = NULL;}
}
double withdraw(Account *a,double amt)
{if(amt<0 || amt>a->balance)return 0.0;double balance = a->balance;sleep(1);balance -= amt;a->balance = balance;return amt;
}double deposit(Account *a,double amt){if(amt<0)return 0.0;double balance = a->balance;sleep(1);balance -= amt;a->balance = balance;return amt;}double get_balance(Account *a){return a->balance;}
  • accouont_test.c
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>
#include"account.h"typedef struct{char name[20];Account *account;double amt;
}OperArg;// 定义取款操作的线程运行函数
void * withdraw_fn(void *arg)
{OperArg *oa = (OperArg*)arg;double amt = withdraw(oa->account,oa->amt);printf("%8s(0x%lx) withdraw %f from account %d\n",oa->name,pthread_self(),amt,oa->account->code  );
}
//定义存款操作的线程运行函数
void* deposit_fn(void *arg)
{OperArg *oa = (OperArg*)arg;double amt = deposit(oa->account,oa->amt);printf("%8s(0x%lx) deposit %f from account %d\n",oa->name,pthread_self(),amt,oa->account->code  );return (void*)0;
}
// 定义检查银行账户的线程运行函数
void  check_fn(void *arg)
{return (void*)0;
}
int main(void)
{int err;pthread_t boy,girl;Account *a = create_account(100001,10000);OperArg o1,o2;strcpy(o1.name,"boy");o1.account = a;o1.amt = 10000;strcpy(o2.name,"girl");o2.account = a;o2.amt = 10000;// 同时启动两个线程去运行同一个账户if((err=pthread_create(&boy,NULL,withdraw_fn,(void*)&o1))!=0){perror("pthread create error");}if((err=pthread_create(&girl,NULL,withdraw_fn,(void*)&o2))!=0){perror("pthread create error");}pthread_join(boy,NULL);pthread_join(girl,NULL);printf("account balance:%f\n",get_balance(a));destory_account(a);return 0;
}
  • 线程不安全
    在这里插入图片描述

互斥锁

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

#ifndef __ACCOUNT_H__
#define __ACCOUNT_H__#include<pthread.h>
typedef struct{int code;double balance;// 定义一把互斥锁,用来对多线程操作。银行账户(共享资源)进行加锁的// 建立互斥锁用来锁定一个账户,和账户绑定在一起// 尽量不设置成全局变量,否则可能出现一把锁去锁几百个账户,导致并发性能降低pthread_mutex_t mutex;
}Account;extern Account* create_account(int code,double balance);
extern void destory_account(Account *a);
extern double withdraw(Account *a,double amt);
extern double deposit(Account *a,double amt);
extern double get_balance(Account *a);#endif
  • account.c
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include"account.h"Account* create_account(int code,double balance)
{Account *a = (Account*)malloc(sizeof(Account));assert(a!=NULL);a->code = code;a->balance = balance;// 对互斥锁进行初始化pthread_mutex_init(&a->mutex,NULL);return a;
}
void destory_account(Account *a)
{if(a!=NULL){// 对互斥锁进行销毁pthread_mutex_destroy(&a->mutex);free(a);a = NULL;}
}
double withdraw(Account *a,double amt)
{// 对共享资源进行加锁pthread_mutex_lock(&a->mutex);if(amt<0 || amt>a->balance){// 释放互斥锁pthread_mutex_unlock(&a->mutex);return 0.0;}double balance = a->balance;sleep(1);balance -= amt;a->balance = balance;// 释放互斥锁pthread_mutex_unlock(&a->mutex);return amt;
}double deposit(Account *a,double amt){pthread_mutex_lock(&a->mutex);if(amt<0){pthread_mutex_unlock(&a->mutex);return 0.0;}double balance = a->balance;sleep(1);balance -= amt;a->balance = balance;pthread_mutex_unlock(&a->mutex);return amt;}double get_balance(Account *a){assert(a!=NULL);pthread_mutex_lock(&a->mutex);double balance = a->balance;pthread_mutex_unlock(&a->mutex);return balance;}

在这里插入图片描述

互斥锁属性创建和销毁

在这里插入图片描述

互斥锁进程共享属性操作

在这里插入图片描述

互斥锁类型操作

在这里插入图片描述


#include<pthread.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>int main(int argc,char *argv[])
{pthread_mutex_t mutex;if(argc<2){printf("-usage:%s [error|normal|recursive]\n",argv[0]);exit(1);}// 定义互斥锁属性pthread_mutexattr_t mutexattr;//初始化互斥锁属性pthread_mutexattr_init(&mutexattr);// 设置互斥锁的类型if(!strcmp(argv[1],"error")){printf("error\n");pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_ERRORCHECK);}else if(!strcmp(argv[1],"normal")){printf("normal\n"); 	pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_NORMAL);}else if(!strcmp(argv[1],"recursive")){printf("recursive\n");pthread_mutexattr_settype(&mutexattr,PTHREAD_MUTEX_RECURSIVE);}pthread_mutex_init(&mutex,&mutexattr);// 第一次上锁if(pthread_mutex_lock(&mutex)!=0){printf("lock failure\n");}else{printf("lock success\n");}// 第二次上锁if(pthread_mutex_lock(&mutex)!=0){printf("lock failure\n");}else{printf("lock success\n");}pthread_mutex_unlock(&mutex);pthread_mutex_unlock(&mutex);pthread_mutexattr_destroy(&mutexattr);pthread_mutex_destroy(&mutex);return 0;
}

在这里插入图片描述

读写锁

  • 互斥锁的坏处
    在这里插入图片描述
    一个线程操作时,其他线程不能进行操作
    在这里插入图片描述
读写锁创建和销毁

在这里插入图片描述

读写锁加锁和解锁

在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>// 定义读写锁
pthread_rwlock_t rwlock;int main(int argc,char * argv[])
{if(argc<3){printf("-usage:%s [r|w] [r|w]\n",argv[0]);exit(0);}// 读写锁初始化pthread_rwlock_init(&rwlock,NULL);// 第一次上锁if(!strcmp("r",argv[1])){//加读锁if(pthread_rwlock_rdlock(&rwlock)!=0){printf("first read lock failure\n");}else{printf("first read lock success\n");}}else if(!strcmp("w",argv[1])){// 加写锁if(pthread_rwlock_wrlock(&rwlock)!=0){printf("first write lock failure\n");}else{printf("first write lock success\n 	");}}// 第二次上锁if(!strcmp("r",argv[2])){//加读锁if(pthread_rwlock_rdlock(&rwlock)!=0){printf("second read lock failure\n");}else{printf("second read lock success\n");}}else if(!strcmp("w",argv[2])){// 加写锁if(pthread_rwlock_wrlock(&rwlock)!=0){printf("second write lock failure\n");}else{printf("second write lock success\n 	");}}// 释放读写锁pthread_rwlock_unlock(&rwlock);pthread_rwlock_unlock(&rwlock);return 0;
}

在这里插入图片描述
在这里插入图片描述

  • 修改银行案例互斥锁变为读写锁
#ifndef __ACCOUNT_H__
#define __ACCOUNT_H__#include<pthread.h>
typedef struct{int code;double balance;// 定义读写锁pthread_rwlock_t rwlock;  
}Account;extern Account* create_account(int code,double balance);
extern void destory_account(Account *a);
extern double withdraw(Account *a,double amt);
extern double deposit(Account *a,double amt);
extern double get_balance(Account *a);#endif
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include"account_rw.h"Account* create_account(int code,double balance)
{Account *a = (Account*)malloc(sizeof(Account));assert(a!=NULL);a->code = code;a->balance = balance;// 对读写锁进行初始化pthread_rwlock_init(&a->rwlock,NULL);return a;
}
void destory_account(Account *a)
{if(a!=NULL){// 对读写锁进行销毁pthread_rwlock_destroy(&a->rwlock);free(a);a = NULL;}
}
double withdraw(Account *a,double amt)
{// 添加写锁pthread_rwlock_wrlock(&a->rwlock);if(amt<0 || amt>a->balance){// 释放读写锁pthread_rwlock_unlock(&a->rwlock);return 0.0;}double balance = a->balance;sleep(1);balance -= amt;a->balance = balance;// 释放读写锁pthread_rwlock_unlock(&a->rwlock);return amt;
}double deposit(Account *a,double amt){pthread_rwlock_wrlock(&a->rwlock);if(amt<0){pthread_rwlock_unlock(&a->rwlock);return 0.0;}double balance = a->balance;sleep(1);balance -= amt;a->balance = balance;pthread_rwlock_unlock(&a->rwlock);return amt;}double get_balance(Account *a){assert(a!=NULL);pthread_rwlock_rdlock(&a->rwlock);double balance = a->balance;pthread_rwlock_unlock(&a->rwlock);return balance;}

线程同步—条件变量

  • 先后执行约束问题

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

条件变量创建和销毁

xAQ

条件变量等待操作

在这里插入图片描述

条件变量通知操作

在这里插入图片描述


#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>typedef struct{int res;int is_wait;                      // 用户给出用于判断pthread_cond_t cond;  					// 条件变量ipthread_mutex_t mutex;				// 互斥锁
}Result;
// 启动两个线程,一个用于计算,一个用于看结果
// 计算并将结果放置到Rexult中的线程运行函数
void * set_fn(void *arg)
{int i = 1,sum = 0;Result *r = (Result*)arg;for(;i<=100;i++)sum+=i;r->res = sum;// 对两个线程共享的判断条件进行保护pthread_mutex_lock(&r->mutex);// 判断获得结果的线程是否准备好while(!r->is_wait){ pthread_mutex_unlock(&r->mutex);usleep(100);pthread_mutex_lock(&r->mutex);	}pthread_mutex_unlock(&r->mutex);// 结果已经准备好了,通知唤醒等待的哪个获取结果的线程pthread_cond_broadcast(&r->cond);return (void*)0;
}// 获得结果的线程运行函数
void * get_fn(void * arg)
{Result *r = (Result*)arg;// 对两个线程共享的判断条件进行保护// 两个线程对判断条件是互斥的pthread_mutex_lock(&r->mutex);// 获取结果的线程准备好了r->is_wait = 1;// 获取结果的线程等待pthread_cond_wait(&r->cond,&r->mutex); // 这个实际上就是将自己放入等待队列中// 线程被唤醒后pthread_mutex_unlock(&r->mutex);// 去获取计算的结果int res = r->res;printf("0x%lx get sum is %d\n",pthread_self(),res);return (void*)0;
}int main()
{int err;pthread_t cal,get;Result r;r.is_wait = 0;pthread_cond_init(&r.cond,NULL);pthread_mutex_init(&r.mutex,NULL);// 启动获取结果的线程if((err=pthread_create(&get,NULL,set_fn,(void*)&r))!=0){perror("pthread_create erro");}// 启动计算结果的线程if((err=pthread_create(&cal,NULL,get_fn,(void*)&r))!=0){perror("pthread_create erro");}pthread_join(cal,NULL);pthread_join(get,NULL);pthread_cond_destroy(&r.cond);pthread_cond_destroy(&r.mutex);return 0;
}

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 一个线程计算,2个线程读数据
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>typedef struct{int res;int counter;                      // 用于统计获取结果线程的数量pthread_cond_t cond;  					// 条件变量ipthread_mutex_t mutex;				// 互斥锁
}Result;
// 启动两个线程,一个用于计算,多个用于看结果
// 计算并将结果放置到Rexult中的线程运行函数
void * set_fn(void *arg)
{int i = 1,sum = 0;Result *r = (Result*)arg;for(;i<=100;i++)sum+=i;r->res = sum;// 对两个线程共享的判断条件进行保护pthread_mutex_lock(&r->mutex);// 判断获得结果的线程是否到达指定数量while(r->counter<2){ pthread_mutex_unlock(&r->mutex);usleep(100);pthread_mutex_lock(&r->mutex);	}pthread_mutex_unlock(&r->mutex);// 结果已经准备好了,通知唤醒等待的哪个获取结果的线程   ... 注意这里就不能使用signal,因为要通知多个线程pthread_cond_broadcast(&r->cond);return (void*)0;
}// 获得结果的线程运行函数
void * get_fn(void * arg)
{Result *r = (Result*)arg;// 对两个线程共享的判断条件进行保护// 两个线程对判断条件是互斥的pthread_mutex_lock(&r->mutex);// 获取结果的线程准备好了r->counter++;// 获取结果的线程等待pthread_cond_wait(&r->cond,&r->mutex); // 这个实际上就是将自己放入等待队列中// 线程被唤醒后pthread_mutex_unlock(&r->mutex);// 去获取计算的结果int res = r->res;printf("0x%lx get sum is %d\n",pthread_self(),res);return (void*)0;
}int main()
{int err;pthread_t cal,get,get1;Result r;r.counter = 0;pthread_cond_init(&r.cond,NULL);pthread_mutex_init(&r.mutex,NULL);// 启动获取结果的线程if((err=pthread_create(&get,NULL,get_fn,(void*)&r))!=0){perror("pthread_create erro");}if((err=pthread_create(&get1,NULL,get_fn,(void*)&r))!=0){perror("pthread_create erro");}// 启动计算结果的线程if((err=pthread_create(&cal,NULL,set_fn,(void*)&r))!=0){perror("pthread_create erro");}pthread_join(cal,NULL);pthread_join(get,NULL);pthread_join(get1,NULL);pthread_cond_destroy(&r.cond);pthread_cond_destroy(&r.mutex);return 0;
}

在这里插入图片描述

读者-写者问题(线程同步案例)

在这里插入图片描述

  • 1个写者 1个读者(以前是单向的,现在是双向的)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>typedef struct{int value;pthread_cond_t rc;      // 条件变量  pthread_mutex_t rm;	     // 互斥锁int 					 r_wait;  // 用于判断的条件变量pthread_cond_t wc;pthread_mutex_t wm;int 						w_wait;
}Storage;// 写入数据
void set_data(Storage *s,int value)
{s->value = value;
}// 读取数据
int  get_data(Storage *s)
{return s->value;
}// 写着线程指向的线程运行函数
void * set_th(void *arg)
{Storage *s = (Storage*)arg;int i = 1;for(;i<=3;i++){set_data(s,i+3);printf("0x%lx (%-5d) write data:%d\n",pthread_self(),i,i+3);// 读者线程是否准备好pthread_mutex_lock(&s->rm);while(!s->r_wait){pthread_mutex_unlock(&s->rm);sleep(1);pthread_mutex_lock(&s->rm);}s->r_wait = 0; //为下一次读写做准备 pthread_mutex_unlock(&s->rm);pthread_cond_broadcast(&s->rc);// 写者线程等待阻塞// 等待读者读取完数据后通知唤醒她// 然后继续写入数据pthread_mutex_lock(&s->wm);s->w_wait  =1; //写者线程准备好了pthread_cond_wait(&s->wc,&s->wm);pthread_mutex_unlock(&s->wm);}return (void*)0;
}//读者线程执行的线程运行函数
void* get_th(void *arg)
{Storage *s = (Storage*)arg;int i = 1;for(;i<=3;i++){pthread_mutex_lock(&s->rm);s->r_wait = 1;pthread_cond_wait(&s->rc,&s->rm);pthread_mutex_unlock(&s->rm);// 读者线程被唤醒后从Storage中读取数:据int value = get_data(s);printf("0x%lx(%-5d) read data: %d\n",pthread_self(),i,value);// 判断写者线程是否准备好while(!s->w_wait){pthread_mutex_unlock(&s->wm);sleep(1);pthread_mutex_lock(&s->wm);}// 唤醒写者线程s->w_wait = 0;pthread_mutex_unlock(&s->wm);pthread_cond_broadcast(&s->wc);}return (void*)0;
}int main(void)
{int err;pthread_t rth,wth;Storage s;s.r_wait = 0;s.w_wait = 0;pthread_mutex_init(&s.rm,NULL);pthread_mutex_init(&s.wm,NULL);pthread_cond_init(&s.rc,NULL);pthread_cond_init(&s.wc,NULL);//创建一个读者线程和写者线程if((err=pthread_create(&rth,NULL,get_th,(void*)&s))!=0){perror("pthread create error");}if((err=pthread_create(&wth,NULL,set_th,(void*)&s))!=0){perror("pthread create error");}pthread_join(rth,NULL);pthread_join(wth,NULL);pthread_mutex_destroy(&s.rm);pthread_mutex_destroy(&s.wm);pthread_cond_destroy(&s.rc);pthread_cond_destroy(&s.wc);}

在这里插入图片描述

线程信号量

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 案例

启动三个线程,通过线程信号量来控制其执行次序

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<semaphore.h>// 定义线程信号量
sem_t sem1;
sem_t sem2;void* a_fn(void *arg)
{sem_wait(&sem1);printf("thread a running\n");return (void*)0;
}void* b_fn(void *arg)
{sem_wait(&sem2);printf("thread b running\n");//对线程信号量sem1+1,让阻塞的线程a继续运行sem_post(&sem1);return (void*)0;
}void* c_fn(void* arg)
{printf("thread c running\n");// 释放线程b,对线程信号量sem2+1,让阻塞的线程b继续执行sem_post(&sem2);return (void*)0;
}
int main()
{printf("测试线程信号\n");pthread_t a,b,c;// 线程信号量初始化,初始值为0sem_init(&sem1,0,0); // 第1个0表示此线程信号量在同一个进程中使用sem_init(&sem2,0,0); // 第2个0表示此线程信号量的初值pthread_create(&a,NULL,a_fn,(void*)0);pthread_create(&b,NULL,b_fn,(void*)0);pthread_create(&c,NULL,c_fn,(void*)0);// 等待所有线程完成pthread_join(a,NULL);pthread_join(b,NULL);pthread_join(c,NULL);// 销毁线程信号量sem_destroy(&sem1);sem_destroy(&sem2);return 0;
}

在这里插入图片描述

互斥

PV操作
在这里插入图片描述
在这里插入图片描述

  • 对银行案例使用它线程信号量进行修改
#ifndef __ACCOUNT_H__
#define __ACCOUNT_H__
#include<semaphore.h>
#include<pthread.h>
typedef struct{int code;double balance;// 定义读写锁	pthread_rwlock_t rwlock;  // 定义线程信号量sem_t sem;
}Account;extern Account* create_account(int code,double balance);
extern void destory_account(Account *a);
extern double withdraw(Account *a,double amt);
extern double deposit(Account *a,double amt);
extern double get_balance(Account *a);#endif

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include"account_signal.h"Account* create_account(int code,double balance)
{Account *a = (Account*)malloc(sizeof(Account));assert(a!=NULL);a->code = code;a->balance = balance;// 对读写锁进行初始化 //pthread_rwlock_init(&a->rwlock,NULL);// 初始化线程信号量,初始值设置为1sem_init(&a->sem,0,1);return a;
}
void destory_account(Account *a)
{if(a!=NULL){// 对读写锁进行销毁//pthread_rwlock_destroy(&a->rwlock);// 销毁线程信号量sem_destroy(&a->sem);free(a);a = NULL;}
}
double withdraw(Account *a,double amt)
{// 添加写锁//pthread_rwlock_wrlock(&a->rwlock);// P(1)操作sem_wait(&a->sem);if(amt<0 || amt>a->balance){// 释放读写锁//pthread_rwlock_unlock(&a->rwlock);// V(1)操作sem_post(&a->sem);return 0.0;}double balance = a->balance;sleep(1);balance -= amt;a->balance = balance;// 释放读写锁//pthread_rwlock_unlock(&a->rwlock);// V(1)操作sem_post(&a->sem);return amt;
}double deposit(Account *a,double amt){//	pthread_rwlock_wrlock(&a->rwlock);// P(1)操作sem_wait(&a->sem);if(amt<0){//	pthread_rwlock_unlock(&a->rwlock);// V(1)操作sem_post(&a->sem);return 0.0;}double balance = a->balance;sleep(1);balance -= amt;a->balance = balance;//pthread_rwlock_unlock(&a->rwlock);// V(1)操作sem_post(&a->sem);return amt;}double get_balance(Account *a){assert(a!=NULL);//pthread_rwlock_rdlock(&a->rwlock);// P(1)sem_wait(&a->sem);	double balance = a->balance;//pthread_rwlock_unlock(&a->rwlock);sem_post(&a->sem);return balance;}

#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<string.h>
#include"account_signal.h"typedef struct{char name[20];Account *account;double amt;
}OperArg;// 定义取款操作的线程运行函数
void * withdraw_fn(void *arg)
{OperArg *oa = (OperArg*)arg;double amt = withdraw(oa->account,oa->amt);printf("%8s(0x%lx) withdraw %f from account %d\n",oa->name,pthread_self(),amt,oa->account->code  );
}
//定义存款操作的线程运行函数
void* deposit_fn(void *arg)
{OperArg *oa = (OperArg*)arg;double amt = deposit(oa->account,oa->amt);printf("%8s(0x%lx) deposit %f from account %d\n",oa->name,pthread_self(),amt,oa->account->code  );return (void*)0;
}
// 定义检查银行账户的线程运行函数
void  check_fn(void *arg)
{return (void*)0;
}
int main(void)
{int err;pthread_t boy,girl;Account *a = create_account(100001,10000);OperArg o1,o2;strcpy(o1.name,"boy");o1.account = a;o1.amt = 10000;strcpy(o2.name,"girl");o2.account = a;o2.amt = 10000;// 同时启动两个线程去运行同一个账户if((err=pthread_create(&boy,NULL,withdraw_fn,(void*)&o1))!=0){perror("pthread create error");}if((err=pthread_create(&girl,NULL,withdraw_fn,(void*)&o2))!=0){perror("pthread create error");}pthread_join(boy,NULL);pthread_join(girl,NULL);printf("account balance:%f\n",get_balance(a));destory_account(a);return 0;
}
同步

在这里插入图片描述


#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
typedef struct{int res;sem_t sem;
}Result;
// 启动两个线程,一个用于计算,一个用于看结果
// 计算并将结果放置到Rexult中的线程运行函数
void * set_fn(void *arg)
{int i = 1,sum = 0;Result *r = (Result*)arg;for(;i<=100;i++)sum+=i;r->res = sum;// V(1)操作sem_post(&r->sem);return (void*)0;
}// 获得结果的线程运行函数
void * get_fn(void * arg)
{Result *r = (Result*)arg;// P(1)操作sem_wait(&r->sem);// 去获取计算的结果int res = r->res;printf("0x%lx get sum is %d\n",pthread_self(),res);return (void*)0;
}int main()
{int err;pthread_t cal,get;Result r;sem_init(&r.sem,0,1);// 启动获取结果的线程if((err=pthread_create(&get,NULL,set_fn,(void*)&r))!=0){perror("pthread_create erro");}// 启动计算结果的线程if((err=pthread_create(&cal,NULL,get_fn,(void*)&r))!=0){perror("pthread_create erro");}pthread_join(cal,NULL);pthread_join(get,NULL);sem_destroy(&r.sem);return 0;
}

在这里插入图片描述

死锁

在这里插入图片描述


#include<pthread.h>
#include<string.h>
#include<errno.h>typedef struct
{int value;pthread_mutex_t mutex;
}ResourceA;typedef struct
{int value;pthread_mutex_t mutex;
}ResourceB;typedef struct
{ResourceA *ra;ResourceB *rb;
}Storage;void * a_fun(void *arg)
{Storage *s = (Storage*)arg;// 对ResourceA加锁pthread_mutex_lock(&s->ra->mutex);sleep(1);printf("0x%lx is waiting for ResouceB...\n",pthread_self());// 对ResourceB加锁pthread_mutex_lock(&s->rb->mutex);printf("ResourceA value is:%d\n",s->ra->value);printf("ResourceB value is:%d\n",s->rb->value);pthread_mutex_unlock(&s->ra->mutex);pthread_mutex_unlock(&s->rb->mutex);
}void * b_fun(void *arg)
{Storage *s = (Storage*)arg;// 对ResourceB加锁pthread_mutex_lock(&s->rb->mutex);sleep(1);printf("0x%lx is waiting for ResouceA...\n",pthread_self());// 对ResourceA加锁pthread_mutex_lock(&s->ra->mutex);printf("ResourceA value is:%d\n",s->ra->value);printf("ResourceB value is:%d\n",s->rb->value);pthread_mutex_unlock(&s->ra->mutex);pthread_mutex_unlock(&s->rb->mutex);
}int main(void)
{ResourceA ra;ResourceB rb;ra.value = 100;rb.value = 200;pthread_mutex_init(&ra.mutex,NULL);pthread_mutex_init(&rb.mutex,NULL);Storage s = {&ra,&rb};int err;pthread_t thread_a,thread_b;if((err=pthread_create(&thread_a,NULL,a_fun,(void*)&s))!=0){//fprintf(stderr,"pthread_create:%s\n",strerror("create a"));exit(1);}if((err=pthread_create(&thread_b,NULL,b_fun,(void*)&s))!=0){//fprintf(stderr,"pthread_create:%s\n",strerror("create b"));exit(1);}pthread_join(thread_a,NULL);pthread_join(thread_b,NULL);pthread_mutex_destroy(&ra.mutex);pthread_mutex_destroy(&rb.mutex);return 0;
}

在这里插入图片描述

#include<pthread.h>
#include<string.h>
#include<errno.h>typedef struct
{int value;pthread_mutex_t mutex;
}ResourceA;typedef struct
{int value;pthread_mutex_t mutex;
}ResourceB;typedef struct
{ResourceA *ra;ResourceB *rb;
}Storage;void * a_fun(void *arg)
{Storage *s = (Storage*)arg;// 对ResourceA加锁pthread_mutex_lock(&s->ra->mutex);sleep(1);printf("0x%lx is waiting for ResouceB...\n",pthread_self());// 对ResourceB加锁pthread_mutex_lock(&s->rb->mutex);printf("ResourceA value is:%d\n",s->ra->value);printf("ResourceB value is:%d\n",s->rb->value);pthread_mutex_unlock(&s->ra->mutex);pthread_mutex_unlock(&s->rb->mutex);
}void * b_fun(void *arg)
{Storage *s = (Storage*)arg;// 对ResourceA加锁pthread_mutex_lock(&s->ra->mutex);sleep(1);printf("0x%lx is waiting for ResouceA...\n",pthread_self());// 对ResourceB加锁pthread_mutex_lock(&s->rb->mutex);printf("ResourceA value is:%d\n",s->ra->value);printf("ResourceB value is:%d\n",s->rb->value);pthread_mutex_unlock(&s->ra->mutex);pthread_mutex_unlock(&s->rb->mutex);
}int main(void)
{ResourceA ra;ResourceB rb;ra.value = 100;rb.value = 200;pthread_mutex_init(&ra.mutex,NULL);pthread_mutex_init(&rb.mutex,NULL);Storage s = {&ra,&rb};int err;pthread_t thread_a,thread_b;if((err=pthread_create(&thread_a,NULL,a_fun,(void*)&s))!=0){//fprintf(stderr,"pthread_create:%s\n",strerror("create a"));exit(1);}if((err=pthread_create(&thread_b,NULL,b_fun,(void*)&s))!=0){//fprintf(stderr,"pthread_create:%s\n",strerror("create b"));exit(1);}pthread_join(thread_a,NULL);pthread_join(thread_b,NULL);pthread_mutex_destroy(&ra.mutex);pthread_mutex_destroy(&rb.mutex);return 0;
}

在这里插入图片描述

线程和信号

在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<pthread.h>// 信号处理函数
void sig_handler(int signo)
{printf("pthread is in the sig_hanlder: %lx\n",pthread_self());if(signo==SIGALRM){printf("timeouot...\n");}alarm(2);
}// 线程运行函数
void * th_fn(void *arg)
{if(signal(SIGALRM,sig_handler)==SIG_ERR){perror("signal sigalarm error");}// 在子线程中设置定时器alarm(2);for(int i = 0;i<=100;i++){printf("(%lx) i:%d\n",pthread_self(),i);sleep(1);}return (void*)0;
}int main()
{int err;pthread_t th;pthread_attr_t attr;pthread_attr_init(&attr);// 设置可分离状态属性pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0){perror("pthread create error");}while(1){printf("control thread(%lx) is running...\n",pthread_self());sleep(10);}printf("control thread over\n");return 0;
}

在这里插入图片描述

  • pause():进程暂停等待信号发生

  • sleep(): 进入睡眠,1. 到了时间睡醒了自动运行 2. 信号产生,发送给他,会中断她的睡眠(例如本例子中的中控线程睡眠10秒,明显不够,是被信号叫醒了)

  • 上面的程序说明,当子线程设置的睡眠信号,时间到了,是发送个主控线程而不是本子线程

信号屏蔽函数

在这里插入图片描述
在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<pthread.h>// 信号处理函数
void sig_handler(int signo)
{printf("pthread is in the sig_hanlder: %lx\n",pthread_self());if(signo==SIGALRM){printf("timeouot...\n");}alarm(2);
}// 线程运行函数
void * th_fn(void *arg)
{if(signal(SIGALRM,sig_handler)==SIG_ERR){perror("signal sigalarm error");}// 在子线程中设置定时器alarm(2);for(int i = 0;i<=100;i++){printf("(%lx) i:%d\n",pthread_self(),i);sleep(1);}return (void*)0;
}int main()
{int err;pthread_t th;pthread_attr_t attr;pthread_attr_init(&attr);// 设置可分离状态属性pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0){perror("pthread create error");}sigset_t set;sigemptyset(&set);sigaddset(&set,SIGALRM);// 对主控线程屏蔽SIGALRM信号pthread_sigmask(SIG_SETMASK,&set,NULL);while(1){printf("control thread(%lx) is running...\n",pthread_self());sleep(10);}printf("control thread over\n");return 0;
}

在这里插入图片描述

  • 终止上面的线程的三中

    • 一直运行直到 在这里插入图片描述
  • 主动终止。使用pthread_exit((void*)0)替换(void*)0

  • 利用另外一个线程,发送信号让其终止

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<signal.h>
#include<pthread.h>// 信号处理函数
void sig_handler(int signo)
{printf("pthread is in the sig_hanlder: %lx\n",pthread_self());if(signo==SIGALRM){printf("timeouot...\n");}alarm(2);
}// 线程运行函数
void * th_fn(void *arg)
{if(signal(SIGALRM,sig_handler)==SIG_ERR){perror("signal sigalarm error");}// 在子线程中设置定时器alarm(2);for(int i = 0;i<=100;i++){printf("th1: (%lx) i:%d\n",pthread_self(),i);sleep(1);}return (void*)0;
}void *th_fn2(void* arg)
{pthread_t th1 = (pthread_t)arg;for(int i =1;i<=15;i++){if(i==5){// 终止线程1的运行pthread_cancel(th1);printf("终止线程1的运行\n");alarm(1);}printf("th2: (%lx) i: %d\n",pthread_self(),i);sleep(1);}
}
int main()
{int err;pthread_t th,th2;pthread_attr_t attr;pthread_attr_init(&attr);// 设置可分离状态属性pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0){perror("pthread create error");}if((err=pthread_create(&th2,&attr,th_fn2,(void*)th))!=0){perror("pthread create error");}sigset_t set;sigemptyset(&set);sigaddset(&set,SIGALRM);// 对主控线程屏蔽SIGALRM信号pthread_sigmask(SIG_SETMASK,&set,NULL);while(1){printf("control thread(%lx) is running...\n",pthread_self());sleep(10);}printf("control thread over\n");return 0;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/65219.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

5G学习笔记之Non-Public Network

目录 0. NPN系列 1. 概述 2. SNPN 2.1 SNPN概述 2.2 SNPN架构 2.3 SNPN部署 2.3.1 完全独立 2.3.2 共享PLMN基站 2.3.3 共享PLMN基站和PLMN频谱 3. PNI-NPN 3.1 PNI-NPN概述 3.2 PNI-NPN部署 3.2.1 UPF独立 3.2.2 完全共享 0. NPN系列 1. NPN概述 2. NPN R18 3. 【SNPN系列】S…

leetCode 292Nim游戏

Nim 游戏中的必胜策略分析 在游戏的世界里&#xff0c;有许多看似简单却蕴含着深刻策略的游戏&#xff0c;Nim 游戏就是其中之一。今天&#xff0c;我们就来深入探讨一下这个有趣的游戏&#xff0c;并通过编程来判断先手在给定石头数量下是否能够获胜。 游戏规则 Nim 游戏的…

DevOps实战:用Kubernetes和Argo打造自动化CI/CD流程(1)

DevOps实战&#xff1a;用Kubernetes和Argo打造自动化CI/CD流程&#xff08;1&#xff09; 架构 架构图 本设计方案的目标是在一台阿里云ECS服务器上搭建一个轻量级的Kubernetes服务k3s节点&#xff0c;并基于Argo搭建一套完整的DevOps CI/CD服务平台&#xff0c;包括Argo CD…

Unity3D用正则判断身份证号或邮箱

系列文章目录 unity工具 文章目录 系列文章目录👉前言👉一、正则判断邮箱格式👉二、正则判断身份证号👉壁纸分享👉总结👉前言 C#正则表达式(Regex)是一种用来匹配字符串模式的强大工具。在C#中,可以使用System.Text.RegularExpressions命名空间下的Regex类来处…

清空DNS 缓存

如果遇到修改了host文件&#xff0c;但是IP和域名的映射有问题的情况&#xff0c;可以尝试刷新DNS缓存。 ipconfig/flushdns win建加R建&#xff0c;然后输入cmd&#xff0c;然后回车 然后回车&#xff0c;或者点击确定按钮。 出现如下所示标识清空DNS 缓存成功。

计算机伦理与职业规范1:计算的社会背景

1 第一个阶段&#xff1a;为战争而发展的计算机器 1.1 问题描述 面对全球冲突&#xff0c;一帮数学家开始致力于尽可能快地解决复杂数学问题。冲突双方都会通过无线电发送命令和战略信息&#xff0c;而这些信号也可能被敌方截获。为了防止信息泄露&#xff0c;军方会对信号进…

笔记本通过HDMI转VGA线连接戴尔显示器,wifi不可用或网速变慢

早上开开心心的来使用我的分屏显示器&#xff0c;结果winP开拓展&#xff0c;我的wifi就断掉了&#xff0c;琢磨了好一阵我以为是wifi的问题&#xff0c;发现不进行拓展&#xff0c;网络又好了&#xff0c;一上午就研究这个了&#xff0c;说是hdmi信号干扰了wifi信号啥的额&…

48页PPT|2024智慧仓储解决方案解读

本文概述了智慧物流仓储建设方案的行业洞察、业务蓝图及建设方案。首先&#xff0c;从政策层面分析了2012年至2020年间国家发布的促进仓储业、物流业转型升级的政策&#xff0c;这些政策强调了自动化、标准化、信息化水平的提升&#xff0c;以及智能化立体仓库的建设&#xff0…

计算机网络:应用层 —— 网络应用模式

文章目录 客户—服务器方式和对等方式客户/服务器方式 (C/S方式)工作流程特点 对等方式 (P2P方式)工作流程P2P 应用特点 客户—服务器方式和对等方式 网络应用程序运行在处于网络边缘的不同的端系统上&#xff0c;通过彼此间的通信来共同完成某项任务。 开发一种新的网络应用…

MC服务器异常ip入侵过滤——使用GeoLite2-Country.mmdb离线数据库定位玩家IP归属国家

服务器插件 ipvalidate-for-mcpaper1.21.jar。 在搞一些东西时发现了GeoLite2这个数据库&#xff0c;本质上它应该算是一个离线的ip地理数据库&#xff0c;就是可以查询到IP对应的国家或地区&#xff0c;MAXMIND还提供了更具体的城市数据库&#xff0c;似乎也可以查到ip对应的城…

uniapp——APP读取bin文件,解析文件的数据内容(二)

文章目录 读取bin文件内容&#xff0c;发送给蓝牙设备&#xff1b;上传文件返回数据格式通过URL路径获取文件对象&#xff0c;读取文件的数据内容file对象返回数据格式 读取bin文件内容&#xff0c;发送给蓝牙设备&#xff1b; 上传文件&#xff0c;根据返回路径&#xff0c;解…

【计算机视觉基础CV-图像分类】05 - 深入解析ResNet与GoogLeNet:从基础理论到实际应用

引言 在上一篇文章中&#xff0c;我们详细介绍了ResNet与GoogLeNet的网络结构、设计理念及其在图像分类中的应用。本文将继续深入探讨如何在实际项目中应用这些模型&#xff0c;特别是如何保存训练好的模型、加载模型以及使用模型进行新图像的预测。通过这些步骤&#xff0c;读…

pytorch中torch.nn.functional.normalize、nn.norm和LayerNorm计算中的区别

torch.nn.functional.normalize 计算方式 v v max ⁡ ( ∥ v ∥ p , ϵ ) \mathbf{v} \frac{\mathbf{v}}{\max\left(\|\mathbf{v}\|_p, \epsilon\right)} vmax(∥v∥p​,ϵ)v​ pytorch例子 a torch.arange(12).reshape(3,4).float() torch.nn.functional.normalize(a, d…

sentinel限流+其他

quick-start | Sentinel sentinel 作用 限流 熔断降级 1&#xff0c;限制什么 QPS 并发线程数 2&#xff0c;限制什么 资源&#xff0c;什么资源 服务&#xff0c;方法&#xff0c;接口&#xff0c;或者一段代码 3&#xff0c;实现方式 配置规则 注解 其他 Java常见5种限流…

运行Zr.Admin项目(后端)

1.下载Zr.Admin代码压缩包 https://codeload.github.com/izhaorui/Zr.Admin.NET/zip/refs/heads/main 2.打开项目 我这里装的是VS2022社区版 进入根目录&#xff0c;双击ZRAdmin.sln打开项目 3.安装.net7运行时 我当时下载的代码版本是.net7的 点击安装 点击安装&#xff0…

Linux系统升级OpenSSH 9.8流程

参考链接&#xff1a; openssh最新版本下载地址&#xff1a;Index of /pub/OpenBSD/OpenSSH/portable/ 注意&#xff1a;openssh9.8需要依赖openssl&#xff0c;版本至少为1.1.1。 一、简介 Openssh存在远程代码执行漏洞(CVE-2024-6387)&#xff0c;攻击者可以成功利用该漏…

webrtc-internals调试工具

Google 的 Chrome&#xff08;87 或更高版本&#xff09;WebRTC 内部工具是一套内置于 Chrome 浏览器中的调试工具; webrtc-internals 能够查看有关视频和音频轨道、使用的编解码器以及流的一般质量的详细信息。这些知识对于解决音频和视频质量差的问题非常有帮助。 webrtc-int…

选择屏幕的用法

**************************定义控件*********************************** SELECTION-SCREEN BEGIN OF BLOCK b1 WITH FRAME TITLE text-002. SELECT-OPTIONS bukrs FOR iloa-bukrs . "公司代码 SELECT-OPTIONS swerk FOR iloa-swerk OBLIGATORY . "工厂 SELECT-O…

Webpack在Vue CLI中的应用

webpack 作为目前最流行的项目打包工具&#xff0c;被广泛使用于项目的构建和开发过程中&#xff0c;其实说它是打包工具有点大材小用了&#xff0c;我个人认为它是一个集前端自动化、模块化、组件化于一体的可拓展系统&#xff0c;你可以根据自己的需要来进行一系列的配置和安…

如何用gpt来分析链接里面的内容(比如分析论文链接)和分析包含多个文件中的一块代码

如何用gpt来分析链接里面的内容&#xff0c;方法如下 这里使用gpt4里面有一个网路的功能 点击搜索框下面这个地球的形状即可启动搜索网页模式 然后即可提出问题在搜索框里&#xff1a;发现正确识别和分析了链接里面的内容 链接如下&#xff1a;https://arxiv.org/pdf/2009.1…