抓取资源的网站怎么做/推广关键词
概述
Go的Routines并发模型是基于CSP,如果你看过七周七并发,那么你应该了解。
什么是CSP?
"Communicating Sequential Processes"(CSP)这个词组的含义来自其英文直译以及在计算机科学中的使用环境。
CSP是 Tony Hoare 在1978年提出的,论文地址在:Communicating sequential processes | Communications of the ACM
拆字解释下
Communicating Sequential Processes(CSP)的三个单词:
-
C for Communicating: 通信,什么的通信那?进程/线程/协程的通信。
-
S for Sequenctial: 顺序的,什么的顺序?进程/线程/协程之间执行任务时应该是有顺序的,完全并行执行是理想化的,现实中就是要先指定完第一个或者第一批任务才能执行第二个或者第二批任务。
-
P for Processses: 进程,这个是进程,估计是因为这个概念提出来的时候比较早。我们这儿得抽象一下,Processes指的是进程/线程/协程。
那么我们来总结一下CSP,CSP就是多个能够进行通信,并且按照顺序执行任务的独立进程。这些进程在各自执行自己的任务的时候,还可以通过某种方式是进行通信。
在Golang中就是通过Channel进行通信。
好了,CSP解释完了,我们来看Go中的Channel,另外CSP的参与者Go Routine我在之前的文章中有提到过,大家可以去:逐步学习Go-协程goroutine
这张图就描述了CSP编程模型。
Go中routine代表图中的Process,Channel就是goroutine之间的连接。通道可以让一个goroutine发送信息到另一个goroutine。
Go中的channel
Go中Channel有两种类型:
- 无缓冲Channel(Unbuffered)
- 有缓冲Channel(Buffered)
有缓冲的Channel其实就是一个环形缓冲队列;无缓冲的没有队列,因为读写都会阻塞。
Channel的定义
var channel名称 chan channel类型// 类型自动推断
channel名称 := make(chan channel类型, buffer数量(int可以为0))
COPY
比如:我们可以这样来定义:
// 定义了一个channel,还没有make,不确定是否为有缓冲和无缓冲channel
var ch chan int// 定义了一个chnnel, 容量为0,无缓冲channel
ch := make(chan int, 0)// 定义了一个channel,容量为1,有缓冲channel
ch := make(chan int, 1)
COPY
我们实际使用的时候把Channel理解为队列就可以了。
Go中的Channel有两种类型:
- 无缓冲channel
- 有缓冲channel
无缓冲和有缓冲的特性如下:
- 无缓冲Channel
- 无缓冲Channel没有存储数据的能力
- 发送方向Channel中发送数据的时候,发送方会阻塞直到有接受者接受这个数据
- 无缓冲Channel典型应用就是go协程同步通信
- 无缓冲Channel保证通信双方都要准备好数据交换
- 有缓冲Channel
- 有缓冲Channel需要定义Channel的容量
- 发送方向有缓冲Channel发送数据的时候,只有容量满的时候才会阻塞
- 接收方只有在有缓冲Channel为空时才会阻塞
- 有缓冲通道的典型应用场景是生产者和消费者
Channel的操作
Channel主要支持2中操作:
- 发送(send)
- 接收(recv)
这三种操作在代码中的的定义和使用:
- 发送和接收都使用
<-
来看代码:
// 先定义一个无缓冲channel
ch := make(chan int, 0)
ch := make(chan int)// 发送数据到channel
ch <- 1// 从channel中接收数据
<- ch
COPY
我们看到发送和接收都是使用<-
,差别在于:
- ch在
<-
的左边,操作为发送 - ch在
<-
的右边,操作为接收
另外,channel在使用之前都要先创建,使用完毕后要关闭,分别使用make
和close
关闭。
// 创建相当于分配channel(allocation)
ch := make(chan int, 0)// 关闭channel,释放channel资源
defer close(ch)
COPY
channel创建完直接关闭了还能操作发送和接收吗?
这个问题我们通过写代码来测试,我们先来测试发送,然后再测试接收。
-
发送数据到关闭的Channel
COPYfunc TestUnbufferedChannel_ShouldPanic_whenWriteValueToAClosedChannel(t *testing.T) {f := func() {ch := make(chan int)close(ch)ch <- 1 }assert.Panics(t, f, "should panic") }
运行截图:
我们的UT PASS了表示发生了panic,这就说明我们不能向已经关闭的channel发送数据。
- 在已经关闭的Channel上接收
func TestUnbufferedChannel_ShouldSuccess_whenRecvValueAtAClosedChannel(t *testing.T) {ch := make(chan int)close(ch)var val = <-chassert.Equal(t, 0, val)
}func TestUnbufferedChannel_ShouldSuccess_whenRecvEmptyValueAtAClosedChannel(t *testing.T) {ch := make(chan string)close(ch)var val = <-chassert.Equal(t, "", val)
}
COPY
运行截图:
这两个UT都可以PASS,我只截图了一个PASS,这说明我们可以在一个关闭的channel上接收数据,只是接收到的都是0值。关于0值
要特别说明一下,0值
是针对不同类型的,比如:int的0值
就是0,string的0值
就是空字符串,指针的0值就是nil,看下面代码:
并非“任何后续的接收操作都将立即返回零值”,而是当channel中所有已发送的值都被接收后,接下来的接收操作会立即返回零值。
无缓冲channel
无缓冲通道顾名思义:就是没有数据缓冲能力的Channel,有goroutine向无缓冲Channel发送了数据就必须有另一个goroutine来接受,否则发送的goroutine会阻塞;反之,有goroutine从这个channel接受数据而没有另一个goroutine向这个channel发送,那么接受的goroutine也会阻塞。
应用场景:
- 部分任务需要同步就用无缓冲channel
来看场景代码:
有发送无接受
发送goroutine会被阻塞。
func TestUnbufferedChannel_ShouldWriteTimeout_WhenNoRoutineReadTheChannel(t *testing.T) {// 创建无缓冲channelc := make(chan int)is_timeout := falsetry_to_write_value := 1// whenselect {// 直接向channel中发送case c <- try_to_write_value:case <-time.After(3 * time.Second):// shouldis_timeout = true}assert.True(t, is_timeout)}
COPY
有接受无发送
接收goroutine会被阻塞
func TestUnbufferedChannel_ShouldReadTimeout_WhenNoValueWriteToChannel(t *testing.T) {// 创建无缓冲channelc := make(chan int)is_timeout := falseselect {// 直接接受channel中的数据case <-c:case <-time.After(3 * time.Second):// shouldis_timeout = true}// 三秒后超时assert.True(t, is_timeout)}
COPY
有发送有接受
有发送有接收,一切正常。
func sum(s []int, c chan int) {sum := 0for _, v := range s {sum += v}// 将累加结果发送到channelc <- sum
}func TestUnbufferedChannel_ShouldRecvValues_WhenWriteValueToChannel(t *testing.T) {// 创建无缓冲channelc := make(chan int)// givens := []int{1, 2, 3, 4, 5, 6}// when// 执行数组累加go sum(s[:], c)ret1 := <-c// should// 和应该是21assert.Equal(t, 21, ret1)
}
COPY
使用无缓冲Channel控制并发
// 先定义一个worker函数
// worker函数从无缓冲channel中接收
// 可以接到到数据就执行后面的打印内容
// 打印完成后退出
func worker(id int, lock chan bool) {var shouldRun = <-lockif shouldRun {fmt.Printf("time: %v Worker %d is working\n", time.Now(), id)time.Sleep(time.Second)fmt.Printf("time: %v Worker %d has finished\n", time.Now(), id)}
}func TestUnbufferedChannel_ShouldRunOneByOne_When(t *testing.T) {lock := make(chan bool, 1)// 启动5个goroutine等待释放接收for i := 0; i < 5; i++ {go worker(i, lock)}// 发送5个true到channelfor i := 0; i < 5; i++ {lock <- truetime.Sleep(time.Second)}close(lock)time.Sleep(10 * time.Second)
}
COPY
使用无缓冲Channel实现CompleteFuture.anyOf()
CompleteFuture.anyOf() 是 Java 中的一个函数,它返回一个新的 CompletableFuture,当给定的任何 CompletableFuture 完成时,返回的 CompletableFuture 也完成,并带有完成的 CompletableFuture 的结果。
// future函数使用time.Sleep模拟实际业务处理延迟
// 业务处理完成后将业务数据写入无缓冲Channel
func future(id int, delay time.Duration, resChan chan int) {time.Sleep(delay)fmt.Printf("Hi, I have finished my task, my id is %d\n", id)resChan <- id
}// 接收一系列上面的future, 然后使用go routine启动这些future函数并将结果写入到result channel,最后再返回result channel。
func anyOf(futures ...<-chan int) <-chan int {result := make(chan int)for _, future := range futures {go func(f <-chan int) {result <- <-f}(future)}return result
}func TestAnyOf_ShouldSuccess(t *testing.T) {// 创建无缓冲的 channelresChan1 := make(chan int)resChan2 := make(chan int)resChan3 := make(chan int)// 启动 goroutinesgo future(1, 3*time.Second, resChan1)go future(2, 2*time.Second, resChan2)go future(3, 5*time.Second, resChan3)result := anyOf(resChan1, resChan2, resChan3)assert.Equal(t, 2, <-result)
}
COPY
上面有两个比较让人纠结的语法:
- <-chan int
- result <- <-f
- <-chan int表示只读通道,anyOf只能读取通道内的数据;有了只读就有只写,只写通道
chan<- int
- result <- <-f表示从通道f中接收数据并将数据写入到result通道。这一行相当于执行了
COPYv := <-f result <- v
有缓冲channel
有缓冲channel就是你可以暂时把数据发送到channel,如果channel的缓冲区没有被占用完就不会阻塞,缓冲区被占用完了就被阻塞了。
特性:
- 发送goroutine在缓冲区没有用完之前不会阻塞,缓冲区被使用完了之后发送goroutine就会被阻塞
- 接受goroutine在缓冲区有数据时,不会阻塞,缓冲区没有数据时会被阻塞
有缓冲channel应用场景是什么?
- 任务队列就是最典型的场景,生产者消费者模型
- 其他无缓冲channel搞不定的就用有缓冲channel
实现一个有缓冲channel的RateLimiter
import ("sync""sync/atomic""testing""fmt""time""github.com/stretchr/testify/assert"
)type RateLimiter struct {tokens chan struct{}refillTicker *time.TickercloseCh chan struct{}
}func NewRateLimiter(rate int) *RateLimiter {r := &RateLimiter{tokens: make(chan struct{}, rate),refillTicker: time.NewTicker(time.Second / time.Duration(rate)),closeCh: make(chan struct{}),}go r.refill()return r
}func (r *RateLimiter) refill() {for {select {case <-r.refillTicker.C:select {case r.tokens <- struct{}{}:default:}case <-r.closeCh:r.refillTicker.Stop()return}}
}func (r *RateLimiter) Acquire() {<-r.tokens
}func (r *RateLimiter) TryAcquire() bool {select {case <-r.tokens:return truedefault:return false}
}func (r *RateLimiter) Close() {close(r.closeCh)
}func myTask(id int) {fmt.Printf("time: %v workder %d is working\n", time.Now(), id)time.Sleep(20 * time.Millisecond)fmt.Printf("time: %v workder %d has finished\n", time.Now(), id)
}func TestRateLimiter_ShouldPermitWithBlocking_WhenRequestOnce(t *testing.T) {rateLimiter := NewRateLimiter(100)startTime := time.Now()for i := 0; i < 1; i++ {rateLimiter.TryAcquire()myTask(i)}endTime := time.Now()elapsedTime := endTime.Sub(startTime)fmt.Printf("elapsed time: %v\n", elapsedTime)fmt.Printf("explect time: %v\n", 300*time.Millisecond)assert.True(t, elapsedTime < 300*time.Millisecond)
}func TestRateLimiter_ShouldLimitPermits_WhenGivenLimitedResource(t *testing.T) {var counter int32 = 0rateLimiter := NewRateLimiter(100)wg := sync.WaitGroup{}startTime := time.Now()for i := range 1000 {wg.Add(1)go func() {rateLimiter.Acquire()myTask(i)atomic.AddInt32(&counter, 1)wg.Done()}()}wg.Wait()endTime := time.Now()elapsedTime := endTime.Sub(startTime)fmt.Printf("elapsed time: %v\n", elapsedTime)fmt.Printf("should greater than explect time: %v\n", 10*time.Second)assert.Equal(t, counter, int32(1000))assert.True(t, 10*time.Second < elapsedTime)
}
COPY
实现无缓冲Channel实现Java中的CyclicBarrier
CyclicBarrier 是一个同步工具,它允许一组线程互相等待,直到他们都到达了一个共同的屏障点。在涉及固定大小的线程团队必须偶尔相互等待的程序中,CyclicBarriers 非常有用。之所以称之为“循环”屏障,是因为在等待的线程被释放之后,它可以被重复使用。
- await() 所有的参与者都调用了wait方法后返回或者被中断
我们就实现这个await方法,暂时不支持中断,代码如下:
package mainimport ("fmt""sync""sync/atomic""time"
)// CyclicBarrier 让一组goroutine在到达某个点之后才能继续执行
type CyclicBarrier struct {// 总goroutine数量participant int// 用于等待所有goroutine准备好waitGroup sync.WaitGroup// 无缓冲channel,用于goroutine间同步barrierChan chan struct{}running int32
}// NewCyclicBarrier 创建一个新的CyclicBarrier
func NewCyclicBarrier(participant int) *CyclicBarrier {b := &CyclicBarrier{participant: participant,barrierChan: make(chan struct{}),running: int32(participant),}// 设置等待的goroutine数b.waitGroup.Add(participant)return b
}// 当一个goroutine调用Wait时,
// 它将在屏障处等待,
// 直到所有goroutine都到达这里
func (b *CyclicBarrier) Wait() {// 一个goroutine准备好了b.waitGroup.Done()// 等待所有goroutine都准备好b.waitGroup.Wait()// 当所有goroutine都准备好了,关闭channel进行广播通知if atomic.AddInt32(&b.running, -1) == 0 {close(b.barrierChan)} else {// 等待通知<-b.barrierChan}}// 阻塞调用goroutine直到所有goroutine都调用了Wait方法,
// 屏障开放后,重新置为待关闭状态
func (b *CyclicBarrier) Await() {// 等待屏障开放的信号<-b.barrierChan// 重置屏障状态b.barrierChan = make(chan struct{})b.waitGroup.Add(b.participant)
}func (b *CyclicBarrier) Close() {close(b.barrierChan)
}func main() {// 这里我们设置3个goroutine参与barrier := NewCyclicBarrier(100)for i := 0; i < 100; i++ {go func(i int) {fmt.Printf("Goroutine %d is working...\n", i)// 模拟工作time.Sleep(time.Duration(i+1) * time.Second)fmt.Printf("Goroutine %d reached the barrier.\n", i)barrier.Wait()fmt.Printf("Goroutine %d passed the barrier.\n", i)}(i)}// 主goroutine等待所有goroutine都到达屏障barrier.Await()fmt.Println("All goroutines have passed the barrier")
}
COPY
参考
- go/src/runtime/chan.go at master · golang/go · GitHub
- 逐步学习Go-并发通道chan(channel) – FOF编程网
编写不易,如有问题请评论告知
相关文章:

逐步学习Go-并发通道chan(channel)
概述 Go的Routines并发模型是基于CSP,如果你看过七周七并发,那么你应该了解。 什么是CSP? "Communicating Sequential Processes"(CSP)这个词组的含义来自其英文直译以及在计算机科学中的使用环境。 CSP…...

鸿蒙HarmonyOS应用开发之Node-API开发规范
当传入napi_get_cb_info的argv不为nullptr时,argv的长度必须大于等于传入argc声明的大小。 当argv不为nullptr时,napi_get_cb_info会根据argc声明的数量将JS实际传入的参数写入argv。如果argc小于等于实际JS传入参数的数量,该接口仅会将声明…...

单例模式
文章目录 单例模式特殊类的设计单例模式饿汉模式懒汉模式懒汉VS饿汉懒汉的线程安全单例对象的释放 单例模式 认识单例模式之前先认识一下几种常见的特殊类的设计。 特殊类的设计 设计一个类 只能再堆上创建对象 只能再堆上创建,则通过new来创建对象。 将类的构造函…...

Android OpenMAX - 开篇
Android Media是一块非常庞大的内容,上到APP的书写,中到播放器的实现、封装格式的了解,下到OMX IL层的实现、Decoder的封装,每一块都需要我们下很大的功夫学习。除此之外,我们还要对一些相关的模块进行了解,…...

ubuntu开启ssh服务
1.安装openssh-server sudo apt-get install openssh-server 2.开启服务 sudo servive ssh start 3.设置开机自启动 sudo systemctl enable ssh 4.检查是否开启成功 ps -ef | grep ssh 如使用xshell等连接时失败,可以尝试关闭防火墙: sudo sys…...

测试缺陷定位的基本方法
前后端bug特征 后端: 业务逻辑问题:如任务状态未扭转成功,创建任务失败等数据类问题:如新增的任务在页面没有展示出来等性能类问题:提交任务一直显示创建中、批量操作等待耗时长超时等 前端: 页面显示类…...

【数字图像处理matlab系列】数组索引
【数字图像处理matlab系列】数组索引 【先赞后看养成习惯】【求点赞+关注+收藏】 MATLAB 支持大量功能强大的索引方案,这些索引方案不仅简化了数组操作,而且提高了程序的运行效率。 1. 向量索引 维数为1xN的数组称为行向量。行向量中元素的存取是使用一维索引进行的。因此…...

【2024系统架构设计】案例分析- 3 数据库
目录 一 基础知识 二 真题 一 基础知识 1 ORM ORM(Object—Relationl Mapping...

vue基础——java程序员版(总集)
前言: 这是一个java程序员的vue学习记录。 vue是前端的主流框架,按照如今的就业形式作为后端开发的java程序员也是要有所了解的,下面是本人的vue学习记录,包括vue2的基本使用以及引入element-ui,使用的开发工具…...

Rancher(v2.6.3)——Rancher配置Harbor镜像仓库
Rancher配置Harbor镜像仓库详细说明文档:https://gitee.com/WilliamWangmy/snail-knowledge/blob/master/Rancher/Rancher%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3.md#8rancher%E9%85%8D%E7%BD%AEharbor ps:如果觉得作者写的还行,能够满足您的需…...

C++类和对象、面向对象编程 (OOP)
文章目录 一、封装1.抽象、封装2.类和对象(0)学习视频(1)类的构成(2)三种访问权限(3)struct和class的区别(4)私有的成员变量、共有的成员函数(5)类内可以直接访问私有成员,不需要经过对象 二、继承三、多态1.概念2.多态的满足条件3.多态的使用条件4.多态原理剖析5.纯…...

Introduction to Data Mining 数据挖掘
Why Data Mining? • The Explosive Growth of Data: from terabytes to petabytes — Data collection and data availability ◦ Automated data collection tools, database systems, Web, computerized society — Major sources of abundant data ◦ Business: Web, e-co…...

常用的 Git 命令
初始化一个新的仓库: git init 克隆一个仓库: git clone <仓库地址> 查看文件状态: git status 添加文件到暂存区: git add <文件名> 提交文件到仓库: git commit -m "提交说明" 查看提交历…...

jackson:JSON字符串(String)类型的成员序列化和反序列化
对于如下类型定义TestTaskInfo,props字段为JSON字符串(这在数据库经常用到),可以自由保存各种类型的数据 Data public class TestTaskInfo {private String id;private String props;public TestTaskInfo() {}public TestTaskInfo(String id, String props) {super…...

使用docker的好处???(docker的优势)
标准化环境: Docker通过容器技术封装应用程序及其依赖(如库、配置文件、运行时环境等),确保应用程序在任何环境中都能以一致的方式运行。这种标准化消除了“在我机器上能运行”的问题,因为容器化应用能在开发、测试、生…...

Google AI 肺癌筛查的计算机辅助诊断
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...

【字符串算法题记录】反转字符串中的单词(leetcode),右旋字符串(kama)——双指针以及反转的奇思妙用
反转字符串中的单词 题目链接 思考 这题的思路顺序是:移除多余空格(双指针法)——》反转整个字符串)——》反转字符串中每个单词。 移除多余空格(双指针法) 因为字符串开头也可能有多个字符࿰…...

基于springboot+vue调用百度ai实现车牌号识别功能
百度车牌号识别官方文档 结果视频演示 后端代码 private String getCarNumber(String imagePath, int count) {// 请求urlString url "https://aip.baidubce.com/rest/2.0/ocr/v1/license_plate";try {byte[] imgData FileUtil.readFileByBytes(imagePath);Stri…...

【NTN 卫星通信】 TN和多NTN配合的应用场景
1 场景描述 此场景描述了农村环境,其中MNO (运营商TerrA)仅在城市附近提供本地地面覆盖,而MNO (SatA)提供广泛的NTN覆盖。SatA使用GSO轨道和NGSO轨道上的卫星。SatA与TerrA有漫游协议,允许: 所有TerrA用户的连接,当这些用户不…...

健康餐饮必备!油烟净化器超强洁净餐饮环境
我最近分析了餐饮市场的油烟净化器等产品报告,解决了餐饮业厨房油腻的难题,更加方便了在餐饮业和商业场所有需求的小伙伴们。 在如今注重健康生活的时代,餐饮业不仅需要美味佳肴,更需要一个清洁、舒适的用餐环境。油烟净化器作…...

Redis修改开源协议,6大备胎重见天日
背景:Redis2018年以来修改了多次开源协议,以前是把一些高级功能收费,这次彻底怒了,把核心代码的协议修改为RSALv2和SSPL双重协议,这个修改对普通用户不受影响,是向所有云厂商开炮,以后云厂商将不…...

使用python读取csv文件快速插入postgres数据库
使用python读取csv文件快速插入postgres数据库 下面为完整代码 import pandas as pd import cStringIO import warnings from sqlalchemy import create_engine import sys reload(sys) sys.setdefaultencoding(utf8) warnings.filterwarnings(ignore) engine create…...

【python地图添加指北针和比例尺】
文章目录 1、前言2、代码2.1、指北针2.2、比例尺 3、结果 1、前言 地理信息绘制中添加指北针和比例尺,使得图像更专业。 2、代码 2.1、指北针 def add_north(ax, labelsize18, loc_x0.95, loc_y0.99, width0.06, height0.09, pad0.14):"""画一个…...

VUE3——Proxy API 与VUE2——defineProperty API区别
一、Object.defineProperty 定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象 为什么能实现响应式 通过defineProperty 两个属性,get及set get 属性的 getter 函…...

卷积神经网络(CNN):图像识别的强大工具
目录 1. 引言 2.卷积神经网络的基本原理 2.1.输入层 2.2.卷积层 2.3.池化层 2.4.激活层 2.5.全连接层(可选) 2.6.输出层 3.卷积神经网络的基本结构 4.卷积神经网络的训练过程 5.代码示例 6.总结 1. 引言 在图像处理与计算机视觉领域…...

【Java多线程】1——多线程知识回顾
1 多线程知识回顾 ⭐⭐⭐⭐⭐⭐ Github主页👉https://github.com/A-BigTree 笔记仓库👉https://github.com/A-BigTree/tree-learning-notes 个人主页👉https://www.abigtree.top ⭐⭐⭐⭐⭐⭐ 如果可以,麻烦各位看官顺手点个star…...

音视频处理 - 音频概念详解,码率,采样率,位深度,声道,编码
1. 音频采样 与视频不同,音频的最小单位不是一帧,而是一个采样。 采样是当前一刻声音的声音样本,样本需要经过数字转换才能存储为样本数据。 真实声音是连续的,但是在计算机中,声音是离散且均匀的声音样本。 2. 位深…...

【PLC】PROFIBUS(二):总线协议DP、PA、FMS
1、总线访问协议 (FDL) 1.1、多主通信 多个主设备间,使用逻辑令牌环依次向从设备发送命令。 特征: 主站间使用逻辑令牌环、主从站间使用主从协议主站在一个限定时间内 (Token Hold Time) 对总线有控制权从站只是响应一个主站的请求它们对总线没有控制…...

Mysql配置autocommit实际使用(慎用)
以下内容都是基于MySQL5.7。所有操作建议在MySQL客户端执行。navicat可能会先意想不到的问题 在导入频繁执行update、insert的时候,可以考虑关闭MySQL的自动提交 首先查询当前的状态 1开启 0关闭 select autocommit;设置本次连接关闭自动提交(如果需要永久关闭请修…...

Mac电脑高清媒体播放器:Movist Pro for mac下载
Movist Pro for mac是一款专为Mac操作系统设计的高清媒体播放器,支持多种常见的媒体格式,包括MKV、AVI、MP4等,能够流畅播放高清视频和音频文件。Movist Pro具有强大的解码能力和优化的渲染引擎,让您享受到更清晰、更流畅的观影体…...