【Linux】线程周边002之线程安全

👀樊梓慕:个人主页

 🎥个人专栏:《C语言》《数据结构》《蓝桥杯试题》《LeetCode刷题笔记》《实训项目》《C++》《Linux》《算法》

🌝每一个不曾起舞的日子,都是对生命的辜负


目录

前言

1.Linux线程互斥

1.1互斥量的接口

1.1.1初始化互斥量

 1.1.2销毁动态分配的互斥量

1.1.3互斥量加锁

1.1.4互斥量解锁

1.2利用RAII思想封装一个管理互斥量的对象  

1.3如何保证申请锁的过程是原子的?

2.Linux线程同步

2.1条件变量

2.1.1初始化条件变量

2.1.2销毁动态分配的条件变量

2.1.3等待条件变量满足 

2.1.4唤醒等待的线程

2.2条件变量函数使用规范

3.可重入VS线程安全

3.1概念

3.2常见的线程不安全的情况

3.3常见的线程安全的情况

3.4常见的不可重入的情况

3.5常见的可重入的情况

3.6可重入与线程安全联系

3.7可重入与线程安全区别

4.常见锁概念

4.1死锁

4.2死锁的四个必要条件


前言

本篇文章内容:线程互斥、互斥量的使用、线程同步、条件变量的使用、可重入函数与线程安全相关内容。

欢迎大家📂收藏📂以便未来做题时可以快速找到思路,巧妙的方法可以事半功倍。 

=========================================================================

GITEE相关代码:🌟樊飞 (fanfei_c) - Gitee.com🌟

=========================================================================


1.Linux线程互斥

首先我们先来学习一组概念:

  • 临界资源: 多线程执行流共享的资源叫做临界资源(全局变量)。
  • 临界区: 每个线程内部,访问临界资源的代码,就叫做临界区(访问或修改临界资源的代码)。
  • 互斥: 任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。
  • 原子性: 不会被任何调度机制打断的操作,该操作只有两态,要么完成,要么未完成。

原子性:如果操作只有一条汇编指令,那么该操作就是原子的。

为了更好的理解,这里模拟实现一个抢票系统,我们将记录票的剩余张数的变量定义为全局变量,主线程创建四个新线程,让这四个新线程进行抢票,当票被抢完后这四个线程自动退出。

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

int tickets = 1000;
void *route(void *arg)
{
  const char *name = (char *)arg;
  while (1)
  {
    if (tickets > 0)
    {
      usleep(10000);
      std::cout << name << " get a ticket, remain: " << --tickets << std::endl;
    }
    else
    {
      break;
    }
  }
  std::cout << name << "quit!" << std::endl;
  pthread_exit((void *)0);
}
int main()
{
  pthread_t t1, t2, t3, t4;
  pthread_create(&t1, NULL, route, (void *)"thread 1");
  pthread_create(&t2, NULL, route, (void *)"thread 2");
  pthread_create(&t3, NULL, route, (void *)"thread 3");
  pthread_create(&t4, NULL, route, (void *)"thread 4");

  pthread_join(t1, NULL);
  pthread_join(t2, NULL);
  pthread_join(t3, NULL);
  pthread_join(t4, NULL);
  return 0;
}

 奇怪的是,最后剩余的票数为负值。

我们明明判断了当tickets>0时,才会对总票数--,可为什么这里是负值呢?

  • if语句判断条件为真以后,代码可以并发的切换到其他线程
  • usleep用于模拟漫长业务的过程,在这个漫长的业务过程中,可能有很多个线程会进入该代码段。
  • --tickets操作本身就不是一个原子操作。

--tickets的操作并不是原子操作,因为它对应着三条汇编指令:

  • load:将共享变量tickets从内存加载到寄存器中。
  • update:更新寄存器里面的值,执行-1操作。
  • store:将新值从寄存器写回共享变量tickets的内存地址。

要解决以上问题,需要做到三点: 

  • 代码必须有互斥行为:当代码进入临界区执行时,不允许其他线程进入该临界区。
  • 如果多个线程同时要求执行临界区的代码,并且此时临界区没有线程在执行,那么只能允许一个线程进入该临界区。
  • 如果线程不在临界区中执行,那么该线程不能阻止其他线程进入临界区。

要做到以上三点,本质上就是需要一把锁,进入临界区之前加锁,离开临界区后解锁,Linux上提供的这把锁叫做互斥量mutex


1.1互斥量的接口

有关于互斥量的操作非常简单,初始化互斥量,销毁互斥量,上锁,解锁等,并且我们还可以利用RAII的思想来管理动态分配的互斥量,具体如何使用都可以。


1.1.1初始化互斥量

对互斥量的初始化,我们有两种方式,一种是静态分配,另一种是动态分配。

静态分配就是当互斥量是全局或者static时使用:

pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;//PTHREAD_MUTEX_INITIALIZER是一个宏

对于这种全局或static互斥量,不需要销毁。


动态分配就是当互斥量是局部时,利用pthread_mutex_init函数初始化,然后使用(动态分配的互斥量需要销毁): 

int pthread_mutex_init(pthread_mutex_t *restrict mutex
                , const pthread_mutexattr_t *restrict attr);

参数说明:

  • mutex:需要初始化的互斥量。
  • attr:初始化互斥量的属性,一般设置为nullptr即可。

返回值说明:

  • 互斥量初始化成功返回0,失败返回错误码。

 1.1.2销毁动态分配的互斥量

int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数说明:

  • mutex:需要销毁的互斥量。

返回值说明:

  • 互斥量销毁成功返回0,失败返回错误码。

销毁互斥量需要注意:

  • 使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要销毁。
  • 不要销毁一个已经加锁的互斥量。

1.1.3互斥量加锁

int pthread_mutex_lock(pthread_mutex_t *mutex);

参数说明:

  • mutex:需要加锁的互斥量。

返回值说明:

  • 互斥量加锁成功返回0,失败返回错误码。

调用pthread_mutex_lock时,可能会遇到以下情况:

  1. 互斥量处于未锁状态,该函数会将互斥量锁定,同时返回成功。
  2. 发起函数调用时,其他线程已经锁定互斥量,或者存在其他线程同时申请互斥量,但没有竞争到互斥量,那么pthread_mutex_lock调用会陷入阻塞(执行流被挂起),等待互斥量解锁。

1.1.4互斥量解锁

int pthread_mutex_unlock(pthread_mutex_t *mutex);

参数说明:

  • mutex:需要解锁的互斥量。

返回值说明:

  • 互斥量解锁成功返回0,失败返回错误码。

1.2利用RAII思想封装一个管理互斥量的对象  

#ifndef __LOCK_GUARD_HPP__
#define __LOCK_GUARD_HPP__

#include <iostream>
#include <pthread.h>

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
    {
        pthread_mutex_lock(_mutex); // 构造加锁
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex);
    }
private:
    pthread_mutex_t *_mutex;
};

#endif

 有了这个类,你就可以将互斥量交给该类管理,当该类构造时,进行加锁,当该类析构时解锁。


1.3如何保证申请锁的过程是原子的?

上面大家已经意识到了--和++操作不是原子操作,可能会导致数据不一致问题。

其实为了实现互斥锁操作,大多数体系结构都提供了swap或exchange指令,该指令的作用就是把寄存器和内存单元的数据相交换。

由于只有一条汇编指令,保证了原子性。

加锁和解锁的过程我们可以通过下面的伪代码来理解:

下面我们再以图示来理解:

 图示中我们在内存中定义了一个mutex互斥量,当我们进行加锁时,cpu会原子地将两个数据进行交换, 交换后,内存中变为0,寄存器%al中变为1,并且这个1的值是唯一的,因为数据在内存中时,所有线程都能访问,属于共享的,但是如果转移到CPU内部寄存器中,就属于一个线程私有的了,因为CPU寄存器内部的数据是线程的硬件上下文。

所以这个1的值就是唯一的,当任何一个线程将这个1交换走后(交换是原子的),哪怕此时线程的时间片到了被切换走,这个1也被线程作为硬件上下文带走了,别的线程也拿不到,判断时也会认为该锁被占用了。


2.Linux线程同步

同步: 在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,这就叫做同步。

那么什么情况下需要同步呢?

如果个别的线程竞争能力非常强, 每次都能够申请到锁,但申请到锁之后什么也不做,所以在我们看来这个线程就一直在申请锁和释放锁,这就可能导致其他线程长时间竞争不到锁,引起饥饿问题。

那如何解决呢?

我们可以让线程每次释放锁后,不能立即申请锁,而是去排队,有顺序的申请锁。


2.1条件变量

条件变量就是实现线程同步的解决方案,是用来描述某种资源是否就绪的一种数据化描述。

换句话说,我们可以利用条件变量实现对其他线程的控制。

比如:

  • 控制某个线程在某一条件变量下等待;
  • 条件满足后,对该线程进行唤醒。

2.1.1初始化条件变量

对条件变量的初始化,我们有两种方式,一种是静态分配,另一种是动态分配。

静态分配就是当条件变量是全局或者static时使用:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//PTHREAD_COND_INITIALIZER是一个宏

对于这种全局或static互斥量,不需要销毁。


动态分配就是当条件变量是局部时,利用pthread_cond_init函数初始化,然后使用(动态分配的条件变量需要销毁): 

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

参数说明:

  • cond:需要初始化的条件变量。
  • attr:初始化条件变量的属性,一般设置为nullptr即可。

返回值说明:

  • 条件变量初始化成功返回0,失败返回错误码。

2.1.2销毁动态分配的条件变量

int pthread_cond_destroy(pthread_cond_t *cond);

参数说明:

  • cond:需要销毁的条件变量。

返回值说明:

  • 条件变量销毁成功返回0,失败返回错误码。

销毁互斥量需要注意:

  • 使用PTHREAD_COND_INITIALIZER初始化的条件变量不需要销毁。

2.1.3等待条件变量满足 

int pthread_cond_wait(pthread_cond_t *restrict cond
                , pthread_mutex_t *restrict mutex);

参数说明:

  • cond:让线程在该条件变量下等待。
  • mutex:当前线程所处临界区对应的互斥锁(为什么要传互斥锁???)。

返回值说明:

  • 函数调用成功返回0,失败返回错误码。

2.1.4唤醒等待的线程

int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

区别:

  • pthread_cond_signal函数用于唤醒等待队列中首个线程。
  • pthread_cond_broadcast函数用于唤醒等待队列中的全部线程。

参数说明:

  • cond:唤醒在cond条件变量下等待的线程。

返回值说明:

  • 函数调用成功返回0,失败返回错误码。

等待条件变量满足的函数参数为什么要传互斥锁?

  • 首先你需要明确的是,使用条件变量一定会搭配使用互斥锁,因为线程同步的场景本身就是在互斥的前提下,即两个线程访问同一资源(临界资源),而现在需要保证的是资源使用的顺序性,所以才引入了条件变量。
  • 而如果此时某个线程由于条件并不满足(这个条件不满足一定是临界资源不满足该线程运行的条件),被设置了等待条件变量满足,从而进入了阻塞状态,能使条件满足一定是该临界资源被修改了,从而满足了线程的运行需要,所以此时你就可以唤醒等待的线程。
  • 也就是说我们想要让条件得到满足,就一定会修改临界资源,而如果你在等待条件变量满足的时候,仍然持有着该临界资源的锁,那么就会导致其他能使该临界资源满足线程运行需要的其他线程访问不了该临界资源,所以给等待条件变量满足的函数传入互斥锁的目的就是让这个锁临时被释放,让其他线程可以访问该临界资源

总结:

  • 等待的时候往往是在临界区内等待的,当该线程进入等待的时候,互斥锁会自动释放,而当该线程被唤醒时,又会自动获得对应的互斥锁。
  • 条件变量需要配合互斥锁使用,其中条件变量是用来完成同步的,而互斥锁是用来完成互斥的。
  • pthread_cond_wait函数有两个功能,一就是让线程在特定的条件变量下等待,二就是让线程释放对应的互斥锁。

2.2条件变量函数使用规范

等待条件变量的代码

pthread_mutex_lock(&mutex);
while (条件为假)
	pthread_cond_wait(&cond, &mutex);
修改条件
pthread_mutex_unlock(&mutex);

唤醒等待线程的代码

pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(&cond);
pthread_mutex_unlock(&mutex);

3.可重入VS线程安全

3.1概念

  • 线程安全: 多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现线程安全问题。
  • 重入: 同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则是不可重入函数。

注意: 线程安全讨论的是线程执行代码时是否安全,重入讨论的是函数被重入进入。

3.2常见的线程不安全的情况

  • 不保护共享变量的函数。
  • 函数状态随着被调用,状态发生变化的函数。
  • 返回指向静态变量指针的函数。
  • 调用线程不安全函数的函数。

3.3常见的线程安全的情况

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的。
  • 类或者接口对于线程来说都是原子操作。
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性。

3.4常见的不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的。
  • 调用了标准I/O库函数,标准I/O可以的很多实现都是以不可重入的方式使用全局数据结构。
  • 可重入函数体内使用了静态的数据结构。

3.5常见的可重入的情况

  • 不使用全局变量或静态变量。
  • 不使用malloc或者new开辟出的空间。
  • 不调用不可重入函数。
  • 不返回静态或全局数据,所有数据都由函数的调用者提供。
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据。

3.6可重入与线程安全联系

  • 函数是可重入的,那就是线程安全的。
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题。
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的。

3.7可重入与线程安全区别

  • 可重入函数是线程安全函数的一种。
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的。
  • 如果对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数的锁还未释放则会产生死锁,因此是不可重入的。

可重入函数一定是线程安全的,函数不可重入是引发线程安全问题的一种常见情况。 


4.常见锁概念

4.1死锁

死锁(Deadlock)是数据库系统、操作系统或并发编程中常见的一种现象,它指的是两个或两个以上的进程(或线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法向前推进。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

举例:假设有两个进程P1和P2,它们都需要两个资源R1和R2。如果P1获得了R1并请求R2,同时P2获得了R2并请求R1,那么这两个进程都会阻塞等待对方释放资源,从而导致死锁。

单执行流可能产生死锁吗?

单执行流也有可能产生死锁,如果某一执行流连续申请了两次锁,那么此时该执行流就会被挂起。

因为该执行流第一次申请锁的时候是申请成功的,但第二次申请锁时因为该锁已经被申请过了,于是申请失败导致被挂起直到该锁被释放时才会被唤醒,但是这个锁本来就在自己手上,自己现在处于被挂起的状态根本没有机会释放锁,所以该执行流将永远不会被唤醒,此时该执行流也就处于一种死锁的状态。

4.2死锁的四个必要条件

  1. 互斥条件: 一个资源每次只能被一个执行流使用。
  2. 请求与保持条件: 一个执行流因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件: 一个执行流已获得的资源,在未使用完之前,不能强行剥夺。
  4. 循环等待条件: 若干执行流之间形成一种头尾相接的循环等待资源的关系。

注意: 这是死锁的四个必要条件,也就是说只有同时满足了这四个条件才可能产生死锁。

避免死锁

  • 破坏死锁的四个必要条件。
  • 按顺序申请资源。
  • 避免锁未释放的场景。
  • 资源一次性分配。

除此之外,还有一些避免死锁的算法,比如死锁检测算法和银行家算法。


 =========================================================================

如果你对该系列文章有兴趣的话,欢迎持续关注博主动态,博主会持续输出优质内容

🍎博主很需要大家的支持,你的支持是我创作的不竭动力🍎

🌟~ 点赞收藏+关注 ~🌟

=========================================================================

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

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

相关文章

Java实现日志全链路追踪.精确到一次请求的全部流程

广大程序员在排除线上问题时,会经常遇见各种BUG.处理这些BUG的时候日志就格外的重要.只有完善的日志才能快速有效的定位问题.为了提高BUG处理效率.我决定在日志上面优化.实现每次请求有统一的id.通过id能获取当前接口的全链路流程走向. 实现效果如下: 一次查询即可找到所有关…

数据分析入门指南:从基础概念到实际应用(一)

随着数字化时代的来临&#xff0c;数据分析在企业的日常运营中扮演着越来越重要的角色。从感知型企业到数据应用系统的演进&#xff0c;数据驱动的业务、智能优化的业务以及数智化转型成为了企业追求的目标。在这一过程中&#xff0c;数据分析不仅是技术的运用&#xff0c;更是…

竹云助力雁塔城运集团实现西安市城投企业数据资产入表第一单!

近日&#xff0c;雁塔区城运集团联合陕数集团、深圳竹云科技股份有限公司等机构&#xff0c;顺利完成数据资产确权登记和数据资产入表工作&#xff0c;成为西安市首个城投数据资产入表案例&#xff0c;并获得陕西丝路数据交易中心颁发的数据资产登记证书。 近年来&#xff0c;…

使用Vue-cli脚手架创建uni-app项目(Vue2版本)

文章目录 前言准备工作接下来创建我们的 uni-app 项目 前言 uni-app官方说除了HBuilderX可视化界面&#xff0c;也可以使用 cli 脚手架&#xff0c;可以通过 vue-cli 创建 uni-app 项目。 uni-app官网文档 准备工作 需要安装 node.js 与 vue-cli 脚手架 我是用的版本如下 no…

【Python】从基础到进阶(二):了解Python语言基础以及数据类型转换、基础输入输出

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、基本数据类型转换1. 隐式转换2. 显式转换 三、基本输入输出1. 输入&#xff08;input&#xff09;2. 输出&#xff08;print&#xff09;3. 案例&#xff1a;输入姓名、年龄、身高以及体重&#xff0c;计算BMI指…

ICMAN触摸芯片之隔空感应

ICMAN触摸芯片之隔空感应 ICMAN触摸芯片满足工业级设计标准&#xff0c; 可过CS10V&#xff0c;ESD8kV&#xff0c;EFT4kV测试&#xff0c; 有超强稳定性和抗干扰能力 &#xff0c; 多用在普通触摸按键开关、大金属触摸及高灵敏度应用场合。 可根据实际应用&#xff0c;有低…

MWCSH 2024丨美格智能亮相上海世界移动通信大会,加速5G+AIoT应用进程

6月26日—28日全球通信领域最具规模和影响力的通信盛事—2024MWC上海世界移动通信大会在上海新国际博览中心隆重举行。MWC上海是亚洲连接生态系统的风向标&#xff0c;本届大会以“未来先行&#xff08;Future First&#xff09;”为主题&#xff0c;聚焦“超越5G”“人工智能经…

牛客小白月赛97 (个人题解)(待补完)

前言&#xff1a; 前天晚上写的一场牛客上比赛&#xff0c;虽然只写出了三道&#xff0c;但比起之前的成绩感觉自己明显有了一点进步了&#xff0c;继续努力吧&#xff0c; 正文&#xff1a; 链接&#xff1a;牛客小白月赛97_ACM/NOI/CSP/CCPC/ICPC算法编程高难度练习赛_牛客竞…

短信接口API的选择因素?有哪些使用方法?

短信接口API的集成难点是什么&#xff1f;如何保障API安全性&#xff1f; 短信接口API已经成为许多企业和开发者的关键工具&#xff0c;市场上有许多不同的短信接口API可供选择&#xff0c;这使得选择适合的API变得尤为重要。AoKSend将探讨在选择短信接口API时需要考虑的主要因…

vscode的一些使用问题

vscode使用技巧 1、快捷键&#xff08;1&#xff09;打开命令面板&#xff08;2&#xff09;注释&#xff08;3&#xff09;删除行&#xff08;4&#xff09;上下移动光标&#xff08;5&#xff09;光标回退&#xff08;6&#xff09;复制行&#xff08;7&#xff09;插入空白行…

联邦的基础配置

一、联邦的定义 联邦&#xff1a;在AS内部部署全互联的IBGP对等体可以很好解决IBGP路由传递的问题&#xff0c;但是扩展性低&#xff0c;大型网络中会带来沉重负担&#xff0c;针对此问题可以用路由反射器解决&#xff0c;也可以利用联邦解决&#xff0c;联邦也被称为联盟。大…

干货分享:Spring中经常使用的工具类(提示开发效率)

环境&#xff1a;Spring5.3…30 1、资源工具类 ResourceUtils将资源位置解析为文件系统中的文件的实用方法。 读取classpath下文件 File file ResourceUtils.getFile(ResourceUtils.CLASSPATH_URL_PREFIX "logback.xml") ; // ...读取文件系统文件 file Resou…

ABAP 新语法-ITAB[ idx ]、ITAB[ key ]

这段ABAP代码主要演示了使用新的ABAP语法内表表达式的用法&#xff0c;其中核心点如下&#xff1a; 索引和关键字读取&#xff1a; 使用gt_student[1]进行索引读取&#xff0c;获取内表的第一个元素。使用gt_student[id 0000000005 age 15]进行关键字读取&#xff0c;根据指…

电子战学习笔记01:电子战概论

0、写在文前 本人在学习电子战相关理论知识时&#xff0c;一直感觉无从下手&#xff0c;之后在老师的推荐下购买了《EW101&#xff1a;电子战基础》纸质书籍学习&#xff0c;所以将自己的学习笔记在CSDN上记录一下&#xff0c;也供有需要的同学参考。 1、电子战定义 电子战&…

惠海100V 15A HC070N10L TO-252封装 N沟道MOS管 打火机/BMS电源板应用

MOS管的工作原理是基于在P型半导体与N型半导体之间形成的PN结&#xff0c;通过改变栅极电压来调整沟道内载流子的数量&#xff0c;从而改变沟道电阻和源极与漏极之间的电流大小。由于MOS管具有输入电阻高、噪声小、功耗低等优点&#xff0c;它们在大规模和超大规模集成电路中得…

ESP32-C3(基本信息)

ESP32-C3 是一款低功耗、高集成度的 MCU 系统级芯片 (SoC)&#xff0c;它集成了 2.4 GHz Wi-Fi 和低功耗蓝牙 (Bluetooth LE) 无线通信功能&#xff0c;并拥有丰富的外设接口和先进的电源管理机制。 主要特性&#xff1a; 无线通信&#xff1a; 支持 2.4 GHz Wi-Fi (802.11b/…

AI音乐革命:创新的门槛降低与产业未来的挑战

文章目录 每日一句正能量前言整体介绍人机合作AI在音乐创作中的辅助作用人机共同创作的模式实现人机共同创作的可能性伦理和法律考量 伦理道德AI与人类创造力的关系技术发展与人类创造力的平衡社会和文化影响结论 后记AI与音乐的未来交响创新的双刃剑版权与伦理的探讨人机合作的…

GaussDB关键技术原理:高性能(三)

GaussDB关键技术原理&#xff1a;高性能&#xff08;二&#xff09;从查询处理综述对GaussDB的高性能技术进行了解读&#xff0c;本篇将从查询重写RBO、物理优化CBO、分布式优化器、布式执行框架、轻量全局事务管理GTM-lite等五方面对高性能关键技术进行分享。 目录 3 高性能…

深入理解Java核心技术模块化局部变量类型推断

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

【C语言】23.文件操作

由于要对数据进行持久化保存&#xff0c;我们就有了文件。 一、程序文件与数据文件 磁盘&#xff08;硬盘&#xff09;上的文件是文件。 但是在程序设计中&#xff0c;我们⼀般谈的文件有两种&#xff1a;程序文件、数据文件&#xff08;从文件功能的角度来分类的&#xff09…