SlideShare a Scribd company logo
1 of 78
Download to read offline
ВОЗМОЖНОСТИ RUBY 

в конкурентном
программировании
• Обработка запроса в многопоточном сервере (Puma) 

может быть очень долгим
• Как реализована многопоточность в MRI Ruby
• Как работает планировщик и приоритетность
• Что внутри EventMachine
• Когда вам могут понадобиться файберы
• Дальнейшее развитие многопоточности в Ruby
Многопоточность?
В Ruby?
Global Interpreter Lock
(GIL)
Руби, которые смогли
JRuby
Rubinius
MRI Ruby
thread.c
MRI
model 1: Userlevel Thread
Same as traditional ruby thread.
model 2: Native Thread with Global VM lock
Using pthread and Ruby threads run concurrent.
model 3: Native Thread with fine grain lock
Using pthread and Ruby threads run concurrent or parallel.
YARV Thread Design
MRI
model 1: Userlevel Thread
Same as traditional ruby thread.
model 2: Native Thread with Global VM lock
Using pthread and Ruby threads run concurrent.
model 3: Native Thread with fine grain lock
Using pthread and Ruby threads run concurrent or parallel.
YARV Thread Design
MRI
model 1: Userlevel Thread
Same as traditional ruby thread.
model 2: Native Thread with Global VM lock
Using pthread and Ruby threads run concurrent.
model 3: Native Thread with fine grain lock
Using pthread and Ruby threads run concurrent or parallel.
YARV Thread Design
Стал ли ваш код 

потокобезопасным?
GIL нужен для защиты 

внутреннего состояния MRI
MRI
model 1: Userlevel Thread
Same as traditional ruby thread.
model 2: Native Thread with Global VM lock
Using pthread and Ruby threads run concurrent.
model 3: Native Thread with fine grain lock
Using pthread and Ruby threads run concurrent or parallel.
YARV Thread Design
At Ruby Kaigi 2016, Koichi Sasada
immutable objects can be shared (read) across Guilds
Multi-VM?
GIL
Как устроен GIL?
0.135
fib(29)
• Сборка вьюхи
• Обработка большого количества данных
• …
def show
render text: fib(29)
end
Puma, 5 потоков
$ (curl -i localhost:3000); (curl -i localhost:3000);
HTTP/1.1 200 OK
X-Request-Id: 839cc258-019b-4737-b169-60b05caaceca
X-Runtime: 0.139625
HTTP/1.1 200 OK
X-Request-Id: a3d85a32-53e6-4a5a-99cd-190f6f48f964
X-Runtime: 0.130910
$ (curl -i localhost:3000 &); (curl -i localhost:3000 &);
HTTP/1.1 200 OK
X-Request-Id: 14183c91-7e27-4f7f-be3d-d06814fa6c55
X-Runtime: 0.278702
HTTP/1.1 200 OK
X-Request-Id: 0ff1ed87-3f54-4afd-af34-218601bbafea
X-Runtime: 0.276639
(curl -i localhost:3000 &) x5
1) X-Runtime: 0.682356
2) X-Runtime: 0.682923
3) X-Runtime: 0.481979
4) X-Runtime: 0.412355
5) X-Runtime: 0.414157
Почему так?
и почему именно 0.682356
Thread.new { }
thread_timer(void *p)
while (system_working > 0) {
/* timer function */
ubf_wakeup_all_threads();
timer_thread_function(0);
if (TT_DEBUG) WRITE_CONST(2, "tickn");
/* wait */
timer_thread_sleep(gvl);
}
while (system_working > 0) {
/* timer function */
ubf_wakeup_all_threads();
timer_thread_function(0);
if (TT_DEBUG) WRITE_CONST(2, "tickn");
/* wait */
timer_thread_sleep(gvl);
}
while (system_working > 0) {
/* timer function */
ubf_wakeup_all_threads();
timer_thread_function(0);
if (TT_DEBUG) WRITE_CONST(2, "tickn");
/* wait */
timer_thread_sleep(gvl);
}
timer_thread_function(void *arg)
{
RUBY_VM_SET_TIMER_INTERRUPT(vm->running_thread);
}
while (system_working > 0) {
/* timer function */
ubf_wakeup_all_threads();
timer_thread_function(0);
if (TT_DEBUG) WRITE_CONST(2, "tickn");
/* wait */
timer_thread_sleep(gvl);
}
/* 100ms. 10ms is too small for user level thread scheduling
* on recent Linux (tested on 2.6.35)
*/
#define TIME_QUANTUM_USEC (100 * 1000)
Приоритетность
Thread 1
priority = 1 priority = 0
Thread 2
1
2
3
if (th->priority > 0)
limits_us <<= th->priority;
Else
limits_us >>= -th->priority;
if (th->status == THREAD_RUNNABLE)
th->running_time_us += TIME_QUANTUM_USEC;
rb_thread_schedule_limits(limits_us)
100ms * 2
priority
rb_thread_schedule_limits(unsigned long limits_us)
{
if (th->running_time_us >= limits_us) {
/* … */
}
}
А что если мы хотим сами управлять потоками?
Fiber
For example, on the
* 32bit POSIX OS, ten or twenty thousands Fiber can be created.
• быстрое переключение (смена контекста)
• ручное управление
вы можете написать свой сервер
на файберах и евентмашине
goliath
Goliath is an open source version of the non-blocking (asynchronous) 

Ruby web server framework.
Что такое EventMachine?
(и причем тут файберы)
EM.run do
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p res.response }
end
void EventMachine_t::Run()
{
while (RunOnce()) ;
}
_UpdateTime();
_RunTimers();
_AddNewDescriptors();
_ModifyDescriptors();
_RunEpollOnce();
_DispatchHeartbeats();
_CleanupSockets();
if (bTerminateSignalReceived)
return false;
_UpdateTime();
_RunTimers();
_AddNewDescriptors();
_ModifyDescriptors();
_RunEpollOnce();
_DispatchHeartbeats();
_CleanupSockets();
if (bTerminateSignalReceived)
return false;
_UpdateTime();
_RunTimers();
_AddNewDescriptors();
_ModifyDescriptors();
_RunEpollOnce();
_DispatchHeartbeats();
_CleanupSockets();
if (bTerminateSignalReceived)
return false;
_UpdateTime();
_RunTimers();
_AddNewDescriptors();
_ModifyDescriptors();
_RunEpollOnce();
_DispatchHeartbeats();
_CleanupSockets();
if (bTerminateSignalReceived)
return false;
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
_UpdateTime();
_RunTimers();
_AddNewDescriptors();
_ModifyDescriptors();
_RunEpollOnce();
_DispatchHeartbeats();
_CleanupSockets();
if (bTerminateSignalReceived)
return false;
_UpdateTime();
_RunTimers();
_AddNewDescriptors();
_ModifyDescriptors();
_RunEpollOnce();
_DispatchHeartbeats();
_CleanupSockets();
if (bTerminateSignalReceived)
return false;
_UpdateTime();
_RunTimers();
_AddNewDescriptors();
_ModifyDescriptors();
_RunEpollOnce();
_DispatchHeartbeats();
_CleanupSockets();
if (bTerminateSignalReceived)
return false;
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
EM.run do
fiber = Fiber.new do
current_fiber = Fiber.current
p 1
res = EventMachine::HttpRequest.new("https://www.ruby-lang.org/").aget
res.callback { p 4; current_fiber.resume(5) }
p 2
puts Fiber.yield
p 6
end
p 0
fiber.resume
p 3
end
em-synchrony
Fiber

• yield
• resume
enumerator =
Fiber.new do
val = 0
loop do
val += 1
Fiber.yield(val)
end
end
enumerator.resume # => 1
enumerator.resume # => 2
enumerator.resume # => 3
Вывод
Спасибо
achempion

More Related Content

What's hot

Building a desktop app with HTTP::Engine, SQLite and jQuery
Building a desktop app with HTTP::Engine, SQLite and jQueryBuilding a desktop app with HTTP::Engine, SQLite and jQuery
Building a desktop app with HTTP::Engine, SQLite and jQuery
Tatsuhiko Miyagawa
 
AnyMQ, Hippie, and the real-time web
AnyMQ, Hippie, and the real-time webAnyMQ, Hippie, and the real-time web
AnyMQ, Hippie, and the real-time web
clkao
 
Ruby/rails performance and profiling
Ruby/rails performance and profilingRuby/rails performance and profiling
Ruby/rails performance and profiling
Danny Guinther
 
node.js, javascript and the future
node.js, javascript and the futurenode.js, javascript and the future
node.js, javascript and the future
Jeff Miccolis
 
연구자 및 교육자를 위한 계산 및 분석 플랫폼 설계 - PyCon KR 2015
연구자 및 교육자를 위한 계산 및 분석 플랫폼 설계 - PyCon KR 2015연구자 및 교육자를 위한 계산 및 분석 플랫폼 설계 - PyCon KR 2015
연구자 및 교육자를 위한 계산 및 분석 플랫폼 설계 - PyCon KR 2015
Jeongkyu Shin
 

What's hot (20)

Tuning Solr for Logs
Tuning Solr for LogsTuning Solr for Logs
Tuning Solr for Logs
 
JavaScript Engines and Event Loop
JavaScript Engines and Event Loop JavaScript Engines and Event Loop
JavaScript Engines and Event Loop
 
Node.js streaming csv downloads proxy
Node.js streaming csv downloads proxyNode.js streaming csv downloads proxy
Node.js streaming csv downloads proxy
 
Building a desktop app with HTTP::Engine, SQLite and jQuery
Building a desktop app with HTTP::Engine, SQLite and jQueryBuilding a desktop app with HTTP::Engine, SQLite and jQuery
Building a desktop app with HTTP::Engine, SQLite and jQuery
 
AnyMQ, Hippie, and the real-time web
AnyMQ, Hippie, and the real-time webAnyMQ, Hippie, and the real-time web
AnyMQ, Hippie, and the real-time web
 
Plack at OSCON 2010
Plack at OSCON 2010Plack at OSCON 2010
Plack at OSCON 2010
 
201904 websocket
201904 websocket201904 websocket
201904 websocket
 
JS everywhere 2011
JS everywhere 2011JS everywhere 2011
JS everywhere 2011
 
Plack at YAPC::NA 2010
Plack at YAPC::NA 2010Plack at YAPC::NA 2010
Plack at YAPC::NA 2010
 
ApacheCon 2014 - What's New in Apache httpd 2.4
ApacheCon 2014 - What's New in Apache httpd 2.4ApacheCon 2014 - What's New in Apache httpd 2.4
ApacheCon 2014 - What's New in Apache httpd 2.4
 
Node.js Event Loop & EventEmitter
Node.js Event Loop & EventEmitterNode.js Event Loop & EventEmitter
Node.js Event Loop & EventEmitter
 
Ruby/rails performance and profiling
Ruby/rails performance and profilingRuby/rails performance and profiling
Ruby/rails performance and profiling
 
Web::Scraper
Web::ScraperWeb::Scraper
Web::Scraper
 
node.js, javascript and the future
node.js, javascript and the futurenode.js, javascript and the future
node.js, javascript and the future
 
연구자 및 교육자를 위한 계산 및 분석 플랫폼 설계 - PyCon KR 2015
연구자 및 교육자를 위한 계산 및 분석 플랫폼 설계 - PyCon KR 2015연구자 및 교육자를 위한 계산 및 분석 플랫폼 설계 - PyCon KR 2015
연구자 및 교육자를 위한 계산 및 분석 플랫폼 설계 - PyCon KR 2015
 
Run Node Run
Run Node RunRun Node Run
Run Node Run
 
Python, async web frameworks, and MongoDB
Python, async web frameworks, and MongoDBPython, async web frameworks, and MongoDB
Python, async web frameworks, and MongoDB
 
All you need to know about the JavaScript event loop
All you need to know about the JavaScript event loopAll you need to know about the JavaScript event loop
All you need to know about the JavaScript event loop
 
Server side JavaScript: going all the way
Server side JavaScript: going all the wayServer side JavaScript: going all the way
Server side JavaScript: going all the way
 
Elasticsearch (R)Evolution — You Know, for Search… by Philipp Krenn at Big Da...
Elasticsearch (R)Evolution — You Know, for Search… by Philipp Krenn at Big Da...Elasticsearch (R)Evolution — You Know, for Search… by Philipp Krenn at Big Da...
Elasticsearch (R)Evolution — You Know, for Search… by Philipp Krenn at Big Da...
 

Similar to mri ruby gil

把鐵路開進視窗裡
把鐵路開進視窗裡把鐵路開進視窗裡
把鐵路開進視窗裡
Wei Jen Lu
 
Socket applications
Socket applicationsSocket applications
Socket applications
João Moura
 

Similar to mri ruby gil (20)

No callbacks, No Threads - Cooperative web servers in Ruby 1.9
No callbacks, No Threads - Cooperative web servers in Ruby 1.9No callbacks, No Threads - Cooperative web servers in Ruby 1.9
No callbacks, No Threads - Cooperative web servers in Ruby 1.9
 
No Callbacks, No Threads - RailsConf 2010
No Callbacks, No Threads - RailsConf 2010No Callbacks, No Threads - RailsConf 2010
No Callbacks, No Threads - RailsConf 2010
 
Fisl - Deployment
Fisl - DeploymentFisl - Deployment
Fisl - Deployment
 
Deployment de Rails
Deployment de RailsDeployment de Rails
Deployment de Rails
 
Non-blocking I/O, Event loops and node.js
Non-blocking I/O, Event loops and node.jsNon-blocking I/O, Event loops and node.js
Non-blocking I/O, Event loops and node.js
 
Tdc 2013 - Ecossistema Ruby
Tdc 2013 - Ecossistema RubyTdc 2013 - Ecossistema Ruby
Tdc 2013 - Ecossistema Ruby
 
Tuning Elasticsearch Indexing Pipeline for Logs
Tuning Elasticsearch Indexing Pipeline for LogsTuning Elasticsearch Indexing Pipeline for Logs
Tuning Elasticsearch Indexing Pipeline for Logs
 
Complex Made Simple: Sleep Better with TorqueBox
Complex Made Simple: Sleep Better with TorqueBoxComplex Made Simple: Sleep Better with TorqueBox
Complex Made Simple: Sleep Better with TorqueBox
 
Islands: Puppet at Bulletproof Networks
Islands: Puppet at Bulletproof NetworksIslands: Puppet at Bulletproof Networks
Islands: Puppet at Bulletproof Networks
 
gRPC with Scala and Swift
gRPC with Scala and SwiftgRPC with Scala and Swift
gRPC with Scala and Swift
 
把鐵路開進視窗裡
把鐵路開進視窗裡把鐵路開進視窗裡
把鐵路開進視窗裡
 
Reactive programming with Rxjava
Reactive programming with RxjavaReactive programming with Rxjava
Reactive programming with Rxjava
 
Cooking with Chef
Cooking with ChefCooking with Chef
Cooking with Chef
 
Concurrency in ruby
Concurrency in rubyConcurrency in ruby
Concurrency in ruby
 
Server Side Swift: Vapor
Server Side Swift: VaporServer Side Swift: Vapor
Server Side Swift: Vapor
 
Introduction to Apache Kafka
Introduction to Apache KafkaIntroduction to Apache Kafka
Introduction to Apache Kafka
 
JUDCon 2010 Boston : TorqueBox
JUDCon 2010 Boston : TorqueBoxJUDCon 2010 Boston : TorqueBox
JUDCon 2010 Boston : TorqueBox
 
Converting Your Dev Environment to a Docker Stack - php[world]
Converting Your Dev Environment to a Docker Stack - php[world]Converting Your Dev Environment to a Docker Stack - php[world]
Converting Your Dev Environment to a Docker Stack - php[world]
 
Montreal On Rails 5 : Rails deployment using : Nginx, Mongrel, Mongrel_cluste...
Montreal On Rails 5 : Rails deployment using : Nginx, Mongrel, Mongrel_cluste...Montreal On Rails 5 : Rails deployment using : Nginx, Mongrel, Mongrel_cluste...
Montreal On Rails 5 : Rails deployment using : Nginx, Mongrel, Mongrel_cluste...
 
Socket applications
Socket applicationsSocket applications
Socket applications
 

Recently uploaded

Recently uploaded (20)

Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
Wired_2.0_CREATE YOUR ULTIMATE LEARNING ENVIRONMENT_JCON_16052024
 
Secure Software Ecosystem Teqnation 2024
Secure Software Ecosystem Teqnation 2024Secure Software Ecosystem Teqnation 2024
Secure Software Ecosystem Teqnation 2024
 
5 Reasons Driving Warehouse Management Systems Demand
5 Reasons Driving Warehouse Management Systems Demand5 Reasons Driving Warehouse Management Systems Demand
5 Reasons Driving Warehouse Management Systems Demand
 
Crafting the Perfect Measurement Sheet with PLM Integration
Crafting the Perfect Measurement Sheet with PLM IntegrationCrafting the Perfect Measurement Sheet with PLM Integration
Crafting the Perfect Measurement Sheet with PLM Integration
 
AI Hackathon.pptx
AI                        Hackathon.pptxAI                        Hackathon.pptx
AI Hackathon.pptx
 
OpenChain Webinar: AboutCode and Beyond - End-to-End SCA
OpenChain Webinar: AboutCode and Beyond - End-to-End SCAOpenChain Webinar: AboutCode and Beyond - End-to-End SCA
OpenChain Webinar: AboutCode and Beyond - End-to-End SCA
 
how-to-download-files-safely-from-the-internet.pdf
how-to-download-files-safely-from-the-internet.pdfhow-to-download-files-safely-from-the-internet.pdf
how-to-download-files-safely-from-the-internet.pdf
 
A Deep Dive into Secure Product Development Frameworks.pdf
A Deep Dive into Secure Product Development Frameworks.pdfA Deep Dive into Secure Product Development Frameworks.pdf
A Deep Dive into Secure Product Development Frameworks.pdf
 
architecting-ai-in-the-enterprise-apis-and-applications.pdf
architecting-ai-in-the-enterprise-apis-and-applications.pdfarchitecting-ai-in-the-enterprise-apis-and-applications.pdf
architecting-ai-in-the-enterprise-apis-and-applications.pdf
 
The Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test AutomationThe Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test Automation
 
How to install and activate eGrabber JobGrabber
How to install and activate eGrabber JobGrabberHow to install and activate eGrabber JobGrabber
How to install and activate eGrabber JobGrabber
 
A Guideline to Zendesk to Re:amaze Data Migration
A Guideline to Zendesk to Re:amaze Data MigrationA Guideline to Zendesk to Re:amaze Data Migration
A Guideline to Zendesk to Re:amaze Data Migration
 
Microsoft365_Dev_Security_2024_05_16.pdf
Microsoft365_Dev_Security_2024_05_16.pdfMicrosoft365_Dev_Security_2024_05_16.pdf
Microsoft365_Dev_Security_2024_05_16.pdf
 
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdfThe Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
The Evolution of Web App Testing_ An Ultimate Guide to Future Trends.pdf
 
GraphSummit Stockholm - Neo4j - Knowledge Graphs and Product Updates
GraphSummit Stockholm - Neo4j - Knowledge Graphs and Product UpdatesGraphSummit Stockholm - Neo4j - Knowledge Graphs and Product Updates
GraphSummit Stockholm - Neo4j - Knowledge Graphs and Product Updates
 
OpenChain @ LF Japan Executive Briefing - May 2024
OpenChain @ LF Japan Executive Briefing - May 2024OpenChain @ LF Japan Executive Briefing - May 2024
OpenChain @ LF Japan Executive Briefing - May 2024
 
What is an API Development- Definition, Types, Specifications, Documentation.pdf
What is an API Development- Definition, Types, Specifications, Documentation.pdfWhat is an API Development- Definition, Types, Specifications, Documentation.pdf
What is an API Development- Definition, Types, Specifications, Documentation.pdf
 
Modern binary build systems - PyCon 2024
Modern binary build systems - PyCon 2024Modern binary build systems - PyCon 2024
Modern binary build systems - PyCon 2024
 
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
 
Microsoft 365 Copilot; An AI tool changing the world of work _PDF.pdf
Microsoft 365 Copilot; An AI tool changing the world of work _PDF.pdfMicrosoft 365 Copilot; An AI tool changing the world of work _PDF.pdf
Microsoft 365 Copilot; An AI tool changing the world of work _PDF.pdf
 

mri ruby gil