Linux系统编程---线程同步

一、同步概念

同步即协同步调,按预定的先后次序运行。

协同步调,对公共区域数据【按序】访问,防止数据混乱,产生与时间有关的错误。

数据混乱的原因:

  1. 资源共享(独享资源则不会)
  2. 调度随机(意味着数据访问会出现竞争)
  3. 线程间缺乏必要同步机制

二、锁

1. 互斥锁

linux中提供一把互斥锁mutex(也称之为互斥量)。

建议锁!对公共数据进行保护。所有线程【应该】在访问公共数据前先拿锁再访问。但锁本身不具备强制性。

数据共享导致的混乱pthrd_shared.c

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

//子线程
void *tfn(void *arg)
{
        srand(time(NULL));

        while(1)
        {
                printf("hello ");
                sleep(rand() % 3); //模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
                printf("world\n");
                sleep(rand() % 3);
        }
        return NULL;
}

int main(void)
{
        pthread_t tid;
        srand(time(NULL));

        // 创建线程
        pthread_create(&tid,NULL,tfn,NULL);
        while(1)
        {
                printf("HELLO ");
                sleep(rand() % 3);
                printf("WORLD\n");
                sleep(rand() % 3);
        }

        //回收线程
        pthread_join(tid,NULL);

        return 0;
}

 输出为:

HELLO hello WORLD
world
hello HELLO WORLD
world
HELLO hello WORLD
world
hello HELLO world
hello WORLD
world
HELLO hello world

pthread_mutex_函数

初始化:int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

销毁:int pthread_mutex_destroylock(pthread_mutex_t *mutex);

上锁:int pthread_mutex_lock(pthread_mutex_t *mutex);

try锁:int pthread_mutex_trylock(pthread_mutex_t *mutex);

解锁:int pthread_mutex_unlock(pthread_mutex_t *mutex);

以上函数的返回值都是:成功返回0,失败返回错误号;

restrict关键字

用来限定指针变量。被该关键字限定的指针变量所指向的内存操作,必须由本指针完成。

使用mutex(互斥量、互斥锁)一般步骤:

  1. pthread_mutex_t lock;创建锁
  2. pthread_mutex_init;初始化
  3. pthread_mutex_lock;加锁
  4. 访问共享数据
  5. pthread_mutex_unlock;解锁
  6. pthread_mutex_destroy;销毁锁

初始化互斥量:

pthread_mutex_t mutex;

1. 动态初始化:pthread_mutex_init(&mutex, NULL);

2. 静态初始化:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

注意事项:

1. 尽量保证锁的粒度,越小越好。(访间共享数据前,加锁。访问结束【立即】解锁。)
2. 互斥锁:本质是结构体。我们可以看成整数。初值为1。(pthread_mutex_init(函数调用成功。))

3. 加锁:--操作,阻塞线程。
4. 解锁:++操作,换醒阻塞在锁上的线程。
5. try锁:尝试加锁,成功--。失败,返回。同时设置错误号EBUSY

修改上面pthrd_shared.c的代码,使用锁实现互斥访问共享区:        

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

pthread_mutex_t mutex;//定义一把互斥锁

//子线程
void *tfn(void *arg)
{
        srand(time(NULL));

        while(1)
        {
                pthread_mutex_lock(&mutex);//加锁
                printf("hello ");
                sleep(rand() % 3);//模拟长时间操作共享资源,导致cpu易主,产生与时间有关的错误
                printf("world\n");
                pthread_mutex_unlock(&mutex);//解锁
        
                sleep(rand() % 3);
        }
        return NULL;
}
         
int main(void)
{
        pthread_t tid;
        srand(time(NULL));
        int ret = pthread_mutex_init(&mutex,NULL);//初始化互斥锁
        if (ret != 0)
        {
                fprintf(stderr,"mutex init error:%s\n",strerror(ret));
                exit(1);        
        }

        // 创建线程
        pthread_create(&tid,NULL,tfn,NULL);
        while(1)
        {
                pthread_mutex_lock(&mutex);//加锁
                printf("HELLO ");
                sleep(rand() % 3);
                printf("WORLD\n");
                pthread_mutex_unlock(&mutex);//解锁
                sleep(rand() % 3);
        }

        //回收线程
        pthread_join(tid,NULL);

        pthread_mutex_destroy(&mutex);//销毁互斥锁
        return 0;
}

 输出为:

HELLO WORLD
hello world
HELLO WORLD
hello world
HELLO WORLD
hello world

2. 死锁:对锁使用不恰当导致的现象

1. 对一个锁反复lock。

2. 两个线程,各自持有一把锁,请求另一把。

情况 1:对一个锁反复 lock

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

pthread_mutex_t mutex;

void *lockTwice(void *arg) 
{
    pthread_mutex_lock(&mutex); // 第一次获取锁
    printf("Lock acquired once.\n");
    pthread_mutex_lock(&mutex); // 尝试再次获取同一个锁,导致死锁
    printf("Lock acquired twice.\n");
    pthread_mutex_unlock(&mutex);
    pthread_mutex_unlock(&mutex);

    return NULL;
}

int main() 
{
    pthread_t tid;
    pthread_mutex_init(&mutex, NULL);
    
    pthread_create(&tid, NULL, lockTwice, NULL);
    pthread_join(tid, NULL);

    pthread_mutex_destroy(&mutex);
    return 0;
}

在这个示例中,我们尝试在同一线程中两次锁定同一个互斥锁。因为 pthread_mutex_t 默认是非递归的,第二次尝试锁定会导致线程阻塞,从而产生死锁 

情况 2:两个线程,各自持有一把锁,请求另一把

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

pthread_mutex_t lock1, lock2;

void *thread1Func(void *arg) 
{
    pthread_mutex_lock(&lock1);
    printf("Thread 1 acquired lock 1\n");
    sleep(1); // 增加死锁发生的可能性
    pthread_mutex_lock(&lock2);
    printf("Thread 1 acquired lock 2\n");
    pthread_mutex_unlock(&lock2);
    pthread_mutex_unlock(&lock1);
    return NULL;
}

void *thread2Func(void *arg) {
    pthread_mutex_lock(&lock2);
    printf("Thread 2 acquired lock 2\n");
    sleep(1); // 增加死锁发生的可能性
    pthread_mutex_lock(&lock1);
    printf("Thread 2 acquired lock 1\n");
    pthread_mutex_unlock(&lock1);
    pthread_mutex_unlock(&lock2);
    return NULL;
}

int main() 
{
    pthread_t t1, t2;
    pthread_mutex_init(&lock1, NULL);
    pthread_mutex_init(&lock2, NULL);
    
    pthread_create(&t1, NULL, thread1Func, NULL);
    pthread_create(&t2, NULL, thread2Func, NULL);

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);

    pthread_mutex_destroy(&lock1);
    pthread_mutex_destroy(&lock2);
    return 0;
}

这个示例中,thread1Func 先锁定 lock1,然后尝试锁定 lock2。同时,thread2Func 先锁定 lock2,然后尝试锁定 lock1。这种交叉锁定容易导致死锁,因为每个线程都在等待对方释放另一把锁。 

3. 读写锁rwlock: 

pthread_rwlock_函数

pthread_rwlock_t rwlock;        用于定义一个读写锁变量

pthread_rwlock_init(&rwlock, NULL);

pthread_rwlock_rdlock(&rwlock);         tryrdlock

pthread_rwlock_wrlock(&rwlock);         trywrlock

pthread_rwlock_unlock(&rwlock);

pthread_rwlock_destroy(&rwlock);

参数与互斥锁类似

以上函数的返回值都是:成功返回0,失败返回错误号

 与互斥量类似,但读写锁允许更高的并行性。其特性为:写独占,读共享

  • 锁只有一把。以读方式给数据加锁------读锁。以写方式给数据加锁------写锁。
  • 读共享,写独占。
  • 写锁优先级高。
  • 相较于互斥量而言,当读线程多的时候,提高访问效率

读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高

代码示例验证写锁优先级高:

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

int counter;
pthread_rwlock_t rwlock;//全局的读写锁

//3个线程不定时写统一全局资源,5个线程不定时读统一全局资源
void *th_write(void *arg)
{
        int t;
        int i = (int)arg;
        while (1) 
        {
                pthread_rwlock_wrlock(&rwlock);//以写模式加锁,写独占
                t = counter;
                usleep(1000);
                printf("===write %d: %lu: counter=%d ++counter=%d\n",i,pthread_self(),t,++counter);
                pthread_rwlock_unlock(&rwlock);
                usleep(10000);
        }
        return NULL;
}

void *th_read(void *arg)
{
        int i = (int)arg;
        while (1) 
        {
                pthread_rwlock_rdlock(&rwlock);//读锁共享
                printf("-----read %d: %lu: %d\n",i,pthread_self(),counter);
                pthread_rwlock_unlock(&rwlock);
                usleep(2000);
        }
        return NULL;
}

int main(void)
{
        int i;
        pthread_t tid[8];

        pthread_rwlock_init(&rwlock,NULL);

        for (i = 0;i < 3;i++)
                pthread_create(&tid[i],NULL,th_write,(void*)i);

        for (i = 0; i < 5; i++)
                pthread_create(&tid[i+3],NULL,th_read,(void*)i);
        
        for (i = 0;i< 8; i++)
                pthread_join(tid[i],NULL);

        pthread_rwlock_destroy(&rwlock);

        return 0;
}

三、条件变量

 条件变量本身不是锁!但它也可以造成线程阻塞,通常与互斥锁配合使用。

主要应用函数:

pthread_cond_init函数                      初始化一个条件变量

pthread_cond_destroy函数               销毁一个条件变量        

pthread_cond_wait函数                    阻塞等待一个条件变量

pthread_cond_timedwait函数           限时等待一个条件变量

pthread_cond_signal函数                 唤醒至少一个阻塞在条件变量上的线程

pthread_cond_broadcast函数          唤醒全部阻塞在条件变量上的线程

参数与互斥锁类似

以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。

pthread_cond_t类型      用于定义条件变量

pthread_cond_t cond;

pthread_cond_init函数 

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

参2:attr表条件变量属性,通常为默认值,传NULL即可

也可以使用静态初始化的方法,初始化条件变量:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

pthread_cond_wait函数 

pthread_cond_wait(&cond, &mutex);

阻塞等待一个条件变量

作用:

  1. 阻塞等待条件变量cond(参1)满足
  2. 解锁已经掌握的互斥锁【解锁互斥量】(相当于 pthread_mutex_unlock(&mutex)),1 2两步为一个原子操作(一起执行,不可分割)
  3. 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁,即重新加锁(相当于pthread_mutex_lock(&mutex);)

1. 当调用pthread_cond_wait函数时,它在阻塞等待条件满足时,会解锁

2. 当条件满足后,重新加锁 

3. pthread_cond_signal()、pthread_cond_broadcast()函数会发送条件满足的信号

4. 通过pthread_cond_timewait()函数来限时等待一个条件变量

条件变量的生产者消费者模型分析: 

条件变量实现生产者消费者代码:

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

void err_thread(int ret,char *str)
{
        if (ret != 0)
        {
                fprintf(stderr,"%s:%d\n",str,strerror(str));
                pthread_exit(NULL);
        }
}

//创建共享数据
struct msg
{
        //struct msg 是一个结构体,包含至少两个字段:
        //num(数据)和 next(指向链表中下一个节点的指针)    
        int num;
        struct msg *next;
};

struct mas *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 = 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);//加锁互斥量
        if (head == NULL)
        {
                pthread_cond_wait(&has_data,&mutex);//阻塞等待条件变量,解锁。pthread_connd_wait返回时,重新加锁mutex
        }

        mp = head;
        head = mp->next;

        pthread_mutex_unlock(&mutex);//解锁互斥量
        printf("------consumer:%d\n",mp->num);

        free(mp);
        sleep(rand() % 3);
        }
        return NULL;
}

int main(int argc,char *argv[])
{
        int ret;         
        pthread_t pid,cid;

        srand(time(NULL));

        ret = pthread_create(&pid,NULL,producer,NULL);
        if (ret != 0)
        {
                err_thread(ret,"pthread_create producer 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;
}

输出为:

 ---produce 330
------consumer id:139864563013376:330
---produce 839
------consumer id:139864554620672:839
---produce 126
------consumer id:139864554620672:126

多个消费者使用while做:

if (ret != 0)
        {
                err_thread(ret,"pthread_create consumer error");//消费者
        }

复制多份创建消费者的代码,并且在consumer回调函数中将if (head == NULL)修改为while (head == NULL)。若未进行修改,则出现段错误

输出为:

---produce 783
---produce 284
---produce 115
---produce 203
------consumer id:139795136452352:203
------consumer id:139795144845056:115
---produce 199
------consumer id:139795153237760:199
------consumer id:139795153237760:284
------consumer id:139795136452352:783

条件变量signal注意事项:

pthread_cond_signal(): 唤醒阻塞在条件变量上的 (至少)一个线程。

pthread_cond_broadcast(): 唤醒阻塞在条件变量上的 所有线程。

四、信号量

  • 应用于线程、进程间同步。
  • 相当于 初始化值为 N 的互斥量。 N值,表示可以同时访问共享数据区的线程数

sem_函数:

sem_t sem; 定义类型,本质是结构体

int sem_init(sem_t *sem, int pshared, unsigned int value);

参数:

        sem: 信号量

        pshared: 0: 用于线程间同步

                        1: 用于进程间同步

        value:N值。(指定同时访问的线程数)

sem_destroy();

sem_wait();一次调用,做一次-- 操作, 当信号量的值为 0 时,再次 -- 就会阻塞。

(对比pthread_mutex_lock)

sem_trywait();

sem_timedwait();

sem_post();一次调用,做一次++ 操作,当信号量的值为 N 时, 再次 ++ 就会阻塞。

(对比 pthread_mutex_unlock)

以上6 个函数的返回值都是:成功返回0, 失败返回-1,同时设置errno。

信号量的初值,决定了占用信号量的线程的个数

信号量实现的生产者消费者:

分析:

代码示例:

 /*信号量实现 生产者 消费者问题*/
#include<stdio.h>  
#include<stdlib.h>  
#include<unistd.h>  
#include<string.h>  
#include<pthread.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);                    //生产者将空格子数--,为0则阻塞等待  
         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);                  //消费者将产品数--,为0则阻塞等待  
         printf("-Consume---%d\n", queue[i]);  
         queue[i] = 0;                               //消费一个产品   
         sem_post(&blank_number);                    //消费掉以后,将空格子数++  
   
         i = (i+1) % NUM;  
         sleep(rand()%3);  
     }  
}  
   
int main(int argc, char *argv[])  
{  
     sem_init(&blank_number, 0, NUM);                //初始化空格子信号量为5, 线程间共享--0  
     sem_init(&product_number, 0, 0);                //产品数为0  

     pthread_t pid, cid;  
     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;  
 } 

条件变量和信号量实现生产者消费者模型掌握一个即可

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

监控员工上网用什么软件比较好 八款电脑监控神器送给你

监控员工上网用什么软件比较好 八款电脑监控神器送给你 监控员工上网行为的软件有多种&#xff0c;每款软件都有其独特的功能和优势。现在让我们一起来探寻最佳员工上网监控神器&#xff01; 想知道哪款电脑监控软件最炫酷、最实用吗&#xff1f;来看看这里&#xff0c;为你揭…

36.WEB渗透测试-信息收集-企业信息收集(3)

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 内容参考于&#xff1a; 易锦网校会员专享课 上一个内容&#xff1a;35.WEB渗透测试-信息收集-企业信息收集&#xff08;2&#xff09; 重要信息收集&#xf…

Python 中的递归排列

在 Python 中使用递归计算排列,适合绝对初学者 介绍 有些人发现很难理解递归算法。 这个技巧向绝对初学者展示了如何使用递归查找排列。Python 背景 这个技巧的想法来自一个问答问题:可怜的 OP 花了三天时间“翻头”,试图弄清楚一小段代码如何能够生成输入列表项的所有排列。…

ROS_第一个程序_Hello_world

ROS的第一个项目&#xff1a;输出Hello World 我们将学习如何创建一个简单的ROS&#xff08;Robot Operating System&#xff09;项目&#xff0c;该项目将在终端中输出"Hello World"。我们将使用Python语言进行编程。 环境准备 首先&#xff0c;确保你的计算机已…

【目标检测】基于深度学习的布匹表面缺陷检测(yolov5算法,4类,附代码和数据集)

写在前面: 首先感谢兄弟们的关注和订阅,让我有创作的动力,在创作过程我会尽最大能力,保证作品的质量,如果有问题,可以私信我,让我们携手共进,共创辉煌。(专栏订阅用户订阅专栏后免费提供数据集和源码一份,超级VIP用户不在服务范围之内) 路虽远,行则将至;事虽难,做…

硬件24、嘉立创EDA丝印的优化和调整

1、调整全部丝印的属性 先选中一个丝印&#xff0c;然后右键点击它&#xff0c;选择查找&#xff0c;然后选择查找全部 选择查找全部这个时候可以设置所有丝印在元件的位置了&#xff0c;布局-》属性位置&#xff0c;位号&#xff0c;属性位置设置为上边&#xff0c;这时丝印就…

全志ARM-网络链接

命令扫描周围的WIFI热点 nmcli dev wifi 命令接入网络 nmcli dev wifi connect &#xff08;WiFi名&#xff0c;不要有空格&#xff09;password (WiFi密码) 查看IP地址 ip addr show wlan0或ifconfig 出现successfully就连接成功了

计应2班01

public class Demo {public void sum(double num1 , double num2){System.out.println(num1 num2);} }import org.junit.Test;public class Test1 { // 定义方法 // test sum // testSum // public void // TestTestpublic void testSum(){Demo de…

如何通过文件下发平台,让数据发挥其真正的价值?

银行网点文件下发平台是专门设计用于银行系统内部或与外部机构之间安全、高效地传输和分发文件的系统。目前使用较多的方式是FTP、邮件、物理媒介等&#xff0c;但都存在一定问题&#xff1a; 1、物理媒介&#xff1a;如U盘、光盘等&#xff0c;通过快递服务发送给分支机构&…

面向对象设计与分析(42)工厂方法模式

文章目录 定义示例实际应用 定义 工厂方法模式&#xff0c;定义一个用于创建对象的接口&#xff08;工厂方法&#xff09;&#xff0c;返回对象基类&#xff0c;让子类去实现该接口&#xff0c;从而返回具体的子类对象。 结构 工厂方法模式包含以下主要角色&#xff1a; 抽象…

观成科技:蔓灵花组织加密通信研究分析总结

1.概述 蔓灵花&#xff0c;又名"Bitter"&#xff0c;常对南亚周边及孟加拉湾海域的相关国家发起网络攻击&#xff0c;主要针对巴基斯坦和中国两国。其攻击目标主要包括政府部门、核工业、能源、国防、军工、船舶工业、航空工业以及海运等行业&#xff0c;其主要意图…

【学习笔记】Python 使用 matplotlib 画图

文章目录 安装中文显示折线图、点线图柱状图、堆积柱状图坐标轴断点参考资料 本文将介绍如何使用 Python 的 matplotlib 库画图&#xff0c;记录一些常用的画图 demo 代码 安装 # 建议先切换到虚拟环境中 pip install matplotlib中文显示 新版的 matplotlib 已经支持字体回退…

Django框架之python后端框架介绍

一、网络框架及MVC、MTV模型 1、网络框架 网络框架&#xff08;Web framework&#xff09;是一种软件框架&#xff0c;用于帮助开发人员构建Web应用程序和Web服务。它提供了一系列预先编写好的代码和工具&#xff0c;以简化开发过程并提高开发效率。网络框架通常包括以下功能…

go语言并发实战——日志收集系统(十) 重构tailfile模块实现同时监控多个日志文件

前言 在上一篇文章中&#xff0c;我们实现了通过etcd来同时指定多个不同的有关分区与日志文件的路径&#xff0c;但是锁着一次读取配置的增多&#xff0c;不可避免的出现了一个问题&#xff1a;我们如何来监控多个日志文件&#xff0c;这样原来的tailFile模块相对于当下场景就…

【JavaScript】内置对象 ④ ( Math 内置对象常用方法 | 取绝对值 | 向下取整 | 向上取整 | 四舍五入取整 | 取随机数 )

文章目录 一、Math 内置对象常用方法1、计算绝对值 - Math.abs2、取整计算 - Math.floor 向下取整 / Math.ceil 向上取整 / Math.round 四舍五入3、随机数 - Math.random4、代码示例 - 猜随机数 一、Math 内置对象常用方法 1、计算绝对值 - Math.abs 向 Math.abs() 方法中 传入…

简单的jmeter脚本自动化

1、创建线程组&#xff0c;定义自定义变量&#xff0c;保存请求默认值 2、用csv编写测试用例 备注&#xff1a;如果单元格内本身就有引号&#xff0c;则格式会有点小问题&#xff0c;不能直接修改为csv 用txt打开后 有引号的需要在最外层多包一层引号&#xff0c;每个引号前…

LM1875L-TB5-T 音频功率放大器 PDF中文资料_参数_引脚图

LM1875L-TB5-T 规格信息&#xff1a; 商品类型音频功率放大器 音频功率放大器的类型- 输出类型1-Channel (Mono) 作业电压16V ~ 60V 输出功率25W x 1 4Ω 额外特性过流保护,热保护 UTC LM1875是一款单片功率放大器&#xff0c;可为消费类音频应 用提供极低失真和高品质的…

外星人电脑丢失文件怎么找回?六大方法助你重获希望

对于许多依赖电脑进行日常工作和娱乐活动的用户来说&#xff0c;电脑中存储的文件无疑是宝贵的财富。然而&#xff0c;意外总是难以避免&#xff0c;外星人电脑也不例外。文件丢失、误删、硬盘故障等问题都可能给用户带来不小的困扰。那么&#xff0c;当外星人电脑遭遇文件丢失…

南京邮电大学计算机组成与结构四次实验报告

文章目录 资源链接预览实验一&#xff1a;算术逻辑运算实验实验二&#xff1a;存储器和总线实验实验三&#xff1a;通用寄存器实验实验四&#xff1a;综合实验的调试 资源链接 资源链接 预览 实验一&#xff1a;算术逻辑运算实验 实验二&#xff1a;存储器和总线实验 实验三&…

07 流量回放实现自动化回归测试

在本模块的前四讲里&#xff0c;我向你介绍了可以直接落地的、能够支撑百万并发的读服务的系统架构&#xff0c;包含懒加载缓存、全量缓存&#xff0c;以及数据同步等方案的技术细节。 基于上述方案及细节&#xff0c;你可以直接对你所负责的读服务进行架构升级&#xff0c;将…
最新文章