【Linux】-進程間通信-共享內存(SystemV),詳解接口函數以及原理(使用管道處理同步互斥機制)

在這里插入圖片描述

💖作者:小樹苗渴望變成參天大樹🎈
🎉作者宣言:認真寫好每一篇博客💤
🎊作者gitee:gitee?
💞作者專欄:C語言,數據結構初階,Linux,C++ 動態規劃算法🎄
如 果 你 喜 歡 作 者 的 文 章 ,就 給 作 者 點 點 關 注 吧!

文章目錄

  • 前言
  • 一、共享內存的原理
  • 二、直接代碼
    • 2.1關于共享內存的四大接口
    • 2.2如何通信
  • 三、擴展知識
    • 3.1 看看維護共享內存的結構體屬性
    • 3.2 使用管道來實現同步互斥機制
  • 四、總結


前言

今天我們來講進程間通信的的另一個通信方式,在第一篇講解進程間通信的博客中,博主就提到了SystemV標準的通信方式,我們前面講解的匿名管道和命名管道都是基于文件的,但是共享內存不是基于文件的,他的所有進程間通信最快的,因為他的拷貝少,共享內存的難點就在于他的接口多,復雜,因為SystemV標準下不止一個共享內存,還有消息隊列和信號量,都需要類似的接口,為了可以更好的復用接口函數,接下來博主就來帶大家學習共享內存。


講解邏輯:

  1. 直接原理,講解周邊問題
  2. 通過原理,寫一部分代碼,認識系統接口,進行測試
  3. 擴展代碼去講解

一、共享內存的原理

使用共享內存的目的是讓進程間進行通信,但是進程間通信的本質是讓不同的進程看到同一份資源,由共享內存這個名字可知,這篇共享的資源是一塊內存,計算機中我們一般由的地址要不是虛擬地址,要不是物理地址,想形成可執行程序里面的地址我們目前不談,而虛擬地址是每個進程特有的,所以我們猜測這塊共享內存是物理內存的一塊,因為有了前面的兩次通信方式的鋪墊,我們已經慢慢找到規律了,那博主就以一份圖給大家講解一下共享內存的原理。
在這里插入圖片描述

共享內存的原理很簡單,就上幅這個圖片,但是博主要講一些周邊問題:

  1. 釋放共享內存,先去掛接,再釋放內存,是相反的操作
  2. 上面的操作都是進程直接做的嗎??不是,是直接由os去做的,原因涉及到物理內存了。
  3. 那既然有os去操作的,那么我們去創建,使用或者釋放都需要經過系統調用接口去讓os幫助我們實現
  4. 我們的不同進程通過共享內存進行通信,另外的進程也需要通過共享內存來進行通信,那么共享內存就不止一塊,由許多快,那么這塊共享內存都是需要管理起來的,所以先描述再組織,就對應我們上圖的struct結構體。里面存放的是對共享內存的管理屬性。

所以我們一會對共享內存的使用里面肯定會涉及到這個結構體里面的屬性,等會遇到了一個講一個,現在都講解出來讀者大概率不會理解。

二、直接代碼

我們通過剛才的原理分析,而且這些操作是需要通過系統調用接口的,所以我們一步步的來介紹這些系統調用接口。

2.1關于共享內存的四大接口

一、申請共享內存接口
在這里插入圖片描述

  1. 返回值(用戶層)shmid:此函數申請一塊共享內存,返回共享內存標識符,可以先理解為和文件描述符唯一標志文件一樣的道理。
  2. 第二個參數,是申請共享內存的大小。單位是字節
  3. 第三個參數:共享內存是為了給不同的進程使用,那么使用這塊內存之前,只要由一個進程創建,其他進程拿來用就行了,那這個參數就是控制對共享內存的權限操作,來看我們自己要掌握的權限在這里插入圖片描述
    (1)IPC_CREAT:(單獨使用)如果你申請的共享內存不存在就創建,存在就獲取返回
    (2)IPC_CREAT | IPC_EXCL:如果你申請的共享內存不存在就創建,存在就報錯,這是保證了你創建的共享內存是最新的。IPC_EXCL不單獨使用
    (3)第三個就是傳我們對應的權限,如0666

上面的方式我們再講解文件操作的時候就講解過了,write函數里面需要傳這樣的參數,這些大寫字母起始就是對應的宏。

  1. 第一個參數:通過第三個參數,我們怎么知道這個共享內存存不存在,就好比你怎么保證讓不同的進程看到同一份共享內存是一樣的,此時就有了我們的第一個參數,接下來談談這個key。
    (1)key是一個數字,這個數字是多少不重要。關鍵在于他必須再內核中具有唯一性,能夠讓不同的進程進程唯一標識
    (2)第一個進程可以通過key來創建共享內存。第二個進程之后的進程,只要拿著這個key就可以和第一個進程看到同一個共享內存了
    (3)對于一個已經創好的共享內存,key在哪??大家還記得一個說管理共享內存的結構體嗎,key就在共享內存的描述對象里
    (4)通過第一點想要key去唯一標識共享內存,大家再回想一下命名管道是怎么唯一標識的,是不是通過就和文件名,所以這個key應該也類似于命名管道的標識方式。
    (5)通過第二點,我們通過key創建共享內存,那么第一次創建的時候,這個key怎么有???

我們總結出四個結論和一個問題,問題來到了這個key一開始時怎么產生的了,按照第四點的結論,我們來介紹一下這個函數ftok
在這里插入圖片描述
第一個參數:路徑這個隨便寫
第二個參數,這個是工程id,我們可以隨便去指定是一個數字
返回值(內核層):是一個共享內存標識符

我們上面的兩個參數都是由用戶自己去定義的,所以可能會和系統中的key產生沖突,這個函數是通過一個算法將兩個參數進行運算的出來的這樣的一個key,每次生成的結果都是不一樣的,不是你每次傳的參數一樣計算出來的結果就是一樣的。這樣為什么就可以做到key是唯一的呢,我們的路徑是唯一的,而且第二個參數是我們自己傳,大概率也是唯一的,這樣就導致我們的key是唯一的,而且一旦創建這個key就是這個共享內存所獨有了,如果再生成這個key,只能獲取,不會再創建一個新的了
為什么key不由os自己創建呢,我們自己創建還有可能造成key沖突的問題??
(1)再談談key的時候的第二點我們知道這個我們通過創建共享內存是由一個進程去創建另一個進程去使用就可以,如果這個key是os生成的,創建好的共享內存,那另一個沒有關系的進程怎么獲取這塊共享內存,因為共享內存不是唯一的,所以os里面的key也不是唯一的,所以沒有辦法給另一個進程讓他獲取啊,有的人說傳給另一個進程,這樣就出現蛋生雞的問題,另一個進程要key才能進行通信,但是要key必須先通信,如果共享內存的個數是唯一的,那么可以讓os自己生成,大家自己理解一下
(2)這個key的獲取可以說是用戶的約定,和哪個進程通信只有用戶知道,就是程序員知道,兩個進程使用ftok這個相同的方式就可以獲取唯一的key,因為這兩個參數是唯一的
(3)有的人會說我們將系統自己生成的key通過管道傳給另一個進程就可以了,答案確實可以,但是這樣我們學習共享內存的成本就搞了,還要先學習管道,這樣也不嫩惡搞保證共享內存是一個獨立通信模塊了

大家看到這里對于key的理解應該到位了,但是有一個關鍵的點,key vs shmid

這兩個都是共享內存的標識符,他兩有一個不就行了,key是內核中唯一標識的,shmid只有再進程里唯一標識的,我們操作共享共享內存的函數都是使用shmid。

通過上面的一系列分析,我們來申請一塊共享內存:shmget+ftok
sham.hpp:

#ifndef __COMM_HPP__
#define __COMM_HPP__#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>#include "log.hpp"using namespace std;Log log;const int size = 4096; 
const string pathname="/home/xdh";
const int proj_id = 0x6666;key_t GetKey()
{key_t k = ftok(pathname.c_str(), proj_id);if(k < 0){log(Fatal, "ftok error: %s", strerror(errno));exit(1);}log(Info, "ftok success, key is : 0x%x", k);return k;
}int GetShareMemHelper(int flag)
{key_t k = GetKey();int shmid = shmget(k, size, flag);if(shmid < 0){log(Fatal, "create share memory error: %s", strerror(errno));exit(2);}log(Info, "create share memory success, shmid: %d", shmid);return shmid;
}int CreateShm()//創建共享內存得到標識符shmid,進行了封裝
{return GetShareMemHelper(IPC_CREAT | IPC_EXCL | 0666);
}
#endif

processa.cc:

#include"sham.hpp"
//這是進程a,有這個進程創建共享內存
int main()
{//申請共享內存int shmid=CreateShm();sleep(5);return 0;
}

在這里插入圖片描述

通過結果我們發現,我們第一次運行程序的時候申請了一塊共享內存獲得了共享內存標識符,但是第二次運行的時候顯示就存在了,我們使用ipcs -m 查看共享內存,我們得出結論,進程結束了,我們的共享內存還是存在的,共享內存的生命周期是隨著內核的,不是隨著進程的,通過原理圖也不難理解這點,沒有關閉共享內存,這也可能會造成內存泄漏,類似于malloc。

這里面我們再來研究一個點,我們申請4097個字節大小的空間看看效果
在這里插入圖片描述
我們看到大小是4097,在內核里面,我們的os實際上會給我們的4096*2大小的空間,但是我們只能使用4097,這個大家要記住,所以建議還是申請4096點整數倍,折合人民幣我們內存的頁寬有關系,大家先不用了解。

二、.掛接共享內存:shmat函數
我們的共享內存申請好了,我們就需要將其掛接到我們的地址空間上,就是原理圖上的第二步
在這里插入圖片描述

  1. 第一個參數:就是傳剛才使用shmget函數的返回值即可是共享內存的唯一標識符
  2. 第二個參數:指定掛接到那個位置,我們申請好了共享內存,要掛接到我們進程的地址空間的共享群位置,這么多位置總要找到一個位置的其實位置吧,這樣也方便我們頁表進行映射,所以需要制定,我們在這里傳空指針就好了,意思讓系統自己決定
  3. 第三個參數:是掛接的方式

在這里插入圖片描述
我們在這里傳0進去就好了

  1. 返回值:我們就是把掛接到地址空間的那塊位置的首地址返回出來,讓用戶能拿到,進行操作,所以返回值是void需要強轉,和malloc類似,失敗就返回(void)-1

我們來看代碼實現:

//將共享內存掛接到自己的地址空間char* shmaddr=(char*)shmat(shmid,nullptr,0);if(*shmaddr<0){log(Fatal,"shmat flase:%s",strerror(errno));exit(3);}log(Info,"shmat sucessful:%s",strerror(errno));sleep(3);

在這里插入圖片描述

我們來觀察一下nattch這個屬性,他就是表示這塊共享內存當前的掛接樹,沒調用這個shmat函數之前為0,調用之后為1,而且當進程退出他的掛接數自然的就減少了1

三.去掉掛接關系:shmdt
剛才是因為程序結束,掛接數減少了,但我們有時候程序沒結束就像去掛接,怎么做??我們通過shmdt來去掛接,來看文檔
在這里插入圖片描述

這個函數非常的簡單,就是傳剛才掛接函數返回值就可以了,我們直接來看使用效果:
我們分析,我們3秒后創建共享內存,5秒后掛接進程,掛接數變成1,3秒后,去掛接,掛接數變成1,在3秒后程序終止,

int n=shmdt(shmaddr);if(n<0){log(Fatal,"shmdt flase:%s",strerror(errno));}log(Info,"shmdt sucessful:%s",strerror(errno));sleep(3);

在這里插入圖片描述

和我們預測的一樣,我們的掛接數不一定非得在程序結束才會減1

四.釋放共享內存:shmctl
我們想要將我們的共享概念內存釋放掉使用shmctl
在這里插入圖片描述

第一個參數:共享內存唯一標識符
第三個參數:是一個描述共享內存的狀態和訪問權限的數據結構,也就是我們開頭說的描述共享內存的結構圖,看到key在里面了吧,對于這個參數我們可以傳一個null,因為不需要將狀態獲取到,這是一個輸出型參數和status一樣。
第二個參數:將要采取的動作,就是對第三個參數實行什么樣的操作,有三個操作

在這里插入圖片描述
我們關注的是最后一個,刪除共享內存

來看操作:

 int n1=shmctl(shmid,IPC_RMID,nullptr);if(n1<0){log(Fatal,"shmctl flase:%s",strerror(errno));}log(Info,"shmctl sucessful:%s",strerror(errno));sleep(3);

在這里插入圖片描述

通過結果驗證我們的講解,我們也可以通過ipcrm -m +shmid來刪除共享內存,這個大家下去試試,但是shmctl傳進去的操作不一樣,功能就不一樣,如果傳IPC_STAT,就可以查看屬性。


我們將另一個進程也掛接到這個共享內存上吧,因為申請和釋放進程a幫助我們做了,我們做的就是掛接和去掛接就可以了,來看進程b的代碼:
先展示進程a的代碼:

#include"sham.hpp"
//這是進程a,有這個進程創建共享內存
int main()
{sleep(3);//申請共享內存int shmid=CreateShm();sleep(5);//將共享內存掛接到自己的地址空間char* shmaddr=(char*)shmat(shmid,nullptr,0);if(*shmaddr<0){log(Fatal,"shmat flase:%s",strerror(errno));exit(3);}log(Info,"shmat sucessful:%s",strerror(errno));sleep(3);//去掛接int n=shmdt(shmaddr);if(n<0){log(Fatal,"shmdt flase:%s",strerror(errno));}log(Info,"shmdt sucessful:%s",strerror(errno));sleep(3);//釋放共享內存int n1=shmctl(shmid,IPC_RMID,nullptr);if(n1<0){log(Fatal,"shmctl flase:%s",strerror(errno));}log(Info,"shmctl sucessful:%s",strerror(errno));sleep(3);return 0;
}

進程b:

#include "sham.hpp"int main()
{sleep(3);int shmid=GetShm();//這個函數在sham.hpp里面寫就行了,獲取shmidsleep(5);//將共享內存掛接到自己的地址空間char* shmaddr=(char*)shmat(shmid,nullptr,0);if(*shmaddr<0){log(Fatal,"shmat flase:%s",strerror(errno));exit(3);}log(Info,"shmat sucessful:%s",strerror(errno));sleep(3);//去掛接int n=shmdt(shmaddr);if(n<0){log(Fatal,"shmdt flase:%s",strerror(errno));}log(Info,"shmdt sucessful:%s",strerror(errno));sleep(3);return 0;
}

在這里插入圖片描述

我們也成功看到了掛接數變成了2,上面講解的一切都是讓兩個不同的進程之間看到同一份資源,還沒有開始通信

2.2如何通信

我們通過上面一系列的操作終于實現我們再原理圖講的內容了,該說不說,確實太復雜的,但是這一系列的操作,讓他的通信顯得非常的簡單,我們共享內存就是一塊物理內存,映射到我們進程的地址空間上,我們程序通過這塊地址空間上的地址就可以直接訪問這塊物理空間,此時他就很想malloc申請空間,然后去使用這塊空間的方法很想,我們一起來看操作,讓b寫,a讀

a:

while(true){cout<<"a say@"<<shmaddr<<endl;sleep(1);}

b:

while(true){cout<<"b enter@";fgets(shmaddr,4096,stdin);sleep(1);}

在這里插入圖片描述

結論:

  1. 我們我們兩個進程對這塊空間的操作是你搞你的我搞我的,兩者不受任何影響,所以說明共享內存間是沒有同步互斥機制的
  2. 我們的共享內存是所有進程中通信速度最快,因為拷貝少
  3. 我們的共享內存的數據是用戶自己去維護的,所以這些看到和管道有不同的地方,沒有清空數據,這是需要用戶自己去決定的。

但是我們確實實現了兩個進程間通信了,有問題我們一會來解決。

三、擴展知識

3.1 看看維護共享內存的結構體屬性

我們剛才的參數都是為了描述共享內存的,所以維護共享概念給內存的屬性有哪些呢,剛才其實也大致看到了一些。
在這里插入圖片描述
我們通過代碼看看我們剛才提到一下屬性:

再a進程把通信代碼改成下面的

 int count=0;struct shmid_ds shmds;while(true){sleep(1);if(count==0){shmctl(shmid, IPC_STAT, &shmds);cout << "shm size: " << shmds.shm_segsz << endl;cout << "shm nattch: " << shmds.shm_nattch << endl;printf("shm key: 0x%x\n",  shmds.shm_perm.__key);cout << "shm mode: " << shmds.shm_perm.mode << endl;}count++;}

在這里插入圖片描述

3.2 使用管道來實現同步互斥機制

我們因為目前只學了System V的共享內存,我們想要解決這個問題,還可以使用信號量,但是這個我們不做重點介紹,等有機會我們在給大家講解信號量是怎么解決共享內存的這個缺點,我們今天,就使用管道去解決這個問題吧,因為是不相關的進程,所以使用命名管道。


在這里插入圖片描述

在這里插入圖片描述

這個使用管道的方法其實和共享內存是一點關系沒有,之根據他會阻塞就不會執行下面的代碼,這樣間接控制了。我們后面會簡單介紹一下信號量是怎么解決這個問題的,但是知識帶大家了解一下。

四、總結

今天我們學習了共享內存,學習成本和前面兩個差不多,前面是原理的鋪墊大家不容易理解,但是使用簡單,二共享內存有了前面的原理鋪墊,理解起來不難,但是后面的使用接口對大家來說可能是一個難度,大家下去好好把四大接口函數理解一下,這對博主下一篇講解消息隊列以及信號量有很大幫助,希望大家下來可以去自己實現博主這篇博客上面的內容,我們下篇再見

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/160925.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/160925.shtml
英文地址,請注明出處:http://en.pswp.cn/news/160925.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

中低壓MOSFET 2N7002T 60V 300mA 雙N通道 采用SOT-523封裝形式

2N7002KW小電流雙N通道MOSFET&#xff0c;電壓60V電流300mA&#xff0c;采用SOT-523封裝形式。低Ros (on)的高密度單元設計&#xff0c;堅固可靠&#xff0c;具有高飽和電流能力&#xff0c;ESD防護門HBM2KV。可應用于直流/直流轉換器&#xff0c;電池開關等產品應用上。

Redis JDBC

1、導入依賴&#xff1a; <dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>4.4.3</version></dependency> 2、連接JDBC public class JedisDemo05 {public static void main(String[]…

成為AI產品經理——AI產品經理工作全流程

一、業務背景 背景&#xff1a;日常排球訓練&#xff0c;中考排球項目和排球體測項目耗費大量人力成本和時間成本。 目標&#xff1a;開發一套用于實時檢測排球運動并進行排球墊球計數和姿勢分析的軟件。 二、產品工作流程 我們這里對于產品工作流程的關鍵部分進行講解&…

「Docker」如何在蘋果電腦上構建簡單的Go云原生程序「MacOS」

介紹 使用Docker開發Golang云原生應用程序&#xff0c;使用Golang服務和Redis服務 注&#xff1a;寫得很詳細 為方便我的朋友可以看懂 環境部署 確保已經安裝Go、docker等基礎配置 官網下載鏈接直達&#xff1a;Docker官網下載 Go官網下載 操作步驟 第一步 創建一個…

Java 多線程之 DCL(Double-Checked Locking)

文章目錄 一、概述二、使用方法 一、概述 DCL&#xff08;Double-Checked Locking&#xff09;是一種用于在多線程環境下實現延遲初始化的技術。它結合了懶加載&#xff08;Lazy Initialization&#xff09;和線程安全性&#xff0c;用于在需要時創建單例對象或共享資源。它的…

什么是SEO?(初學者建議收藏)

前言 在這個充滿機遇和挑戰的時代&#xff0c;人們不斷追求著更好的生活和更高的成就。無論是個人還是企業&#xff0c;都需要不斷提升自己的競爭力&#xff0c;才能在激烈的市場競爭中獲得成功。因此&#xff0c;我們需要不斷學習和成長&#xff0c;學會適應變化和面對挑戰。…

汽車智能座艙/智能駕駛SOC -2

第二篇&#xff08;筆記&#xff09;。 未來智能汽車電子電氣將會是集中式架構&#xff08;車載數據中心&#xff09;虛擬化技術&#xff08;提供車載數據中心靈活性和安全性&#xff09;這個幾乎是毋庸置疑的了。國際大廠也否紛紛布局超算芯片和車載數據中心平臺。但是演進需…

日期格式轉化成星期幾部署到linux顯示英文

異常收集 原因&#xff1a;解決辦法仰天大笑出門去&#xff0c;我輩豈是蓬蒿人 傳入一個時間獲取這個時間對應的是星期幾&#xff0c;在開發環境&#xff08;window系統&#xff09;中顯示為星期幾&#xff0c;部署到服務器&#xff08;linux系統&#xff09;中會顯示英文的時間…

serverless開發實戰

.yml格式 YAML&#xff08;YAML Ain’t Markup Language&#xff09;是一種人類可讀的數據序列化格式&#xff0c;它使用簡潔的結構和縮進來表示數據。它被廣泛用于配置文件和數據交換的場景&#xff0c;具有易讀性和易寫性的特點。 serverless.yml配置 在項目的根目錄下增加…

Youtube新手運營——你需要的技巧與工具

對于有跨境意向的內容創作者或者品牌企業來說&#xff0c;YouTube是因其巨大的潛在受眾群和商業價值成為最值得投入變現與營銷計劃的平臺。 據統計&#xff0c;98% 的美國人每月訪問 YouTube&#xff0c;近三分之二的人每天訪問。但是&#xff0c;YouTube還遠未達到過度飽和的…

Leetcode—53.最大子數組和【中等】

2023每日刷題&#xff08;三十四&#xff09; Leetcode—53.最大子數組和 前綴和算法思想 參考靈茶山艾府 實現代碼 #define MAX(a, b) ((a > b) ? (a) : (b)) #define MIN(a, b) ((a < b) ? (a) : (b)) int maxSubArray(int* nums, int numsSize) {int ans INT_…

VMware 16 Pro 安裝以及下載

1、下載地址&#xff1a; https://www.aliyundrive.com/s/nj3PSD4TN9G 2、安裝文件 右擊打開 下一步 密鑰&#xff1a;ZF3R0-FHED2-M80TY-8QYGC-NPKYF 到此&#xff0c;安裝完畢

postgreSQL如何快速查詢大表數據量

文章目錄 場景方案結果 場景 我有一個非常大的表&#xff0c;估計幾百萬或者幾千萬。 我開始使用了 select count(*) from my_table_javapub 方式&#xff0c;查詢非常慢。 如何解決&#xff1f;&#xff1f;&#xff1f; 方案 如果你需要更快地獲取表中的行數&#xff0c…

93.STL-系統內置仿函數

目錄 算術仿函數 關系仿函數 邏輯仿函數 C 標準庫中提供了一些內置的函數對象&#xff0c;也稱為仿函數&#xff0c;它們通常位于 <functional> 頭文件中。以下是一些常見的系統內置仿函數&#xff1a; 算術仿函數 功能描述&#xff1a; 實現四則運算其中negate是一元…

Java游戲之飛翔的小鳥

前言 飛翔的小鳥 小游戲 可以作為 java入門階段的收尾作品 &#xff1b; 需要掌握 面向對象的使用以及了解 多線程&#xff0c;IO流&#xff0c;異常處理&#xff0c;一些java基礎等相關知識。一 、游戲分析 1. 分析游戲邏輯 &#xff08;1&#xff09;先讓窗口顯示出來&#x…

騰訊待辦導出的文件在哪找?支持打開ics文件的提醒待辦工具

您使用過騰訊待辦嗎&#xff1f;如果您在平常使用的提醒待辦工具為騰訊待辦&#xff0c;想必近期您打開這款提醒待辦工具時會看到提示您及時導出數據的提示。騰訊旗下的騰訊待辦應用&#xff0c;應業務發展方向調整將于2023年12月20日全面停止運營并下架該應用。 面對突如其來…

Redis的主從復制及哨兵模式

一、Redis的主從復制 1.1 Redis主從復制定義 主從復制是redis實現高可用的基礎&#xff0c;哨兵模式和集群都是在主從復制的基礎之上實現高可用&#xff1b; 主從復制實現數據的多級備份&#xff0c;以及讀寫分離(主服務器負責寫&#xff0c;從服務器只能讀) 1.2 主從復制流…

ChatGPT 也并非萬能,品牌如何搭上 AIGC「快班車」

內容即產品的時代&#xff0c;所見即所得&#xff0c;所得甚至超越所見。 無論是在公域的電商平臺、社交媒體&#xff0c;還是品牌私域的官網、社群、小程序&#xff0c;品牌如果想與用戶發生連接&#xff0c;內容永遠是最前置的第一要素。 01 當內容被消費過&#xff0c;就…

【python學習】基礎篇-常用模塊-os目錄操作

os模塊提供了許多與操作系統交互的函數&#xff0c;包括操作目錄的函數。 1、導入os模塊&#xff1a; import os2、獲取當前工作目錄&#xff1a;使用os模塊的getcwd()方法獲取當前工作目錄。 current_dir os.getcwd() print(current_dir)3、改變當前工作目錄&#xff1a;使…