31,19 €
Alex Rios, a seasoned Go developer and active community builder, shares his 15 years of expertise in designing large-scale systems through this book. It masterfully cuts through complexity, enabling you to build efficient and secure applications with Go's streamlined syntax and powerful concurrency features.
In this book, you’ll learn how Go, unlike traditional system programming languages (C/C++), lets you focus on the problem by prioritizing readability and elevating developer experience with features like automatic garbage collection and built-in concurrency primitives, which remove the burden of low-level memory management and intricate synchronization.
Through hands-on projects, you'll master core concepts like file I/O, process management, and inter-process communication to automate tasks and interact with your system efficiently. You'll delve into network programming in Go, equipping yourself with the skills to build robust, distributed applications. This book goes beyond the basics by exploring modern practices like logging and tracing for comprehensive application monitoring, and advance to distributed system design using Go to prepare you to tackle complex architectures.
By the end of this book, you'll emerge as a confident Go system programmer, ready to craft high-performance, secure applications for the modern world.
Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:
Seitenzahl: 501
Veröffentlichungsjahr: 2024
System Programming Essentials with Go
System calls, networking, efficiency, and security practices with practical projects in Golang
Alex Rios
Copyright © 2024 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Group Product Manager: Kunal Sawant
Publishing Product Manager: Akash Sharma
Book Project Manager: Prajakta Naik
Senior Editor: Kinnari Chohan
Technical Editor: Rajdeep Chakraborty
Copy Editor: Safis Editing
Proofreader: Kinnari Chohan
Indexer: Tejal Soni
Production Designer: Vijay Kamble
DevRel Marketing Coordinator: Sonia Chauhan
First published: June 2024
Production reference: 1070624
Published by Packt Publishing Ltd.
Grosvenor House
11 St Paul’s Square
Birmingham
B3 1RB, UK
ISBN 978-1-83763-413-2
www.packtpub.com
For Erika, my life partner, who supported me through the long hours and extra effort needed to complete this work. Your love and encouragement have been my constant source of strength.
Alex Rios is an established Brazilian software engineer with a 15-year track record of success in large-scale solution development. He specializes in Go and creates high-throughput systems that address diverse needs across fintech, telecom, and gaming industries. As a staff engineer at Stone Co., Alex applies his expertise using unconventional system designs, ensuring top-notch delivery. Also, he uses his expertise to evaluate books and publications as a technical reviewer. He is an enthusiastic community member, actively participating in its growth and development as Curitiba’s Go meetup organizer. His dedication is evident in his regular presence as a speaker at major national tech events, such as GopherCon Brazil.
I would like to express my gratitude to Elton Minetto for opening this door and making the push for me to write my very first book. I am also immensely grateful to my editor, Kinnari Chohan, for her patience and meticulous attention to detail during the editing process. Your expertise and dedication have greatly enhanced the quality of this book. Lastly, a heartfelt thanks to the Go community for creating such an inspiring and supportive environment.
Natan Streppel has over a decade of experience in the IT field and, in this period, has worked with a range of different technologies. He discovered Golang in 2017 and immediately fell for the language’s simplicity and expressiveness. Since then, he has been using the language and its ecosystem for both professional and non-professional projects. He thrives on tackling challenging problems in distributed systems, loves contributing to the open-source community, and enjoys exploring innovative solutions.
System programming is a critical area of knowledge in software engineering. It matters most for professionals who want to write efficient, low-level code that interacts closely with the operating system. System Programming Essentials with Go is designed to guide you through the principles and practices necessary to stand up in system programming using Go. This book covers a broad range of topics, from basic system programming concepts to advanced techniques, providing a comprehensive toolkit for tackling real-world system programming challenges.
This book is tailored for software engineers, architects, and developers who possess a basic understanding of programming and seek to deepen their knowledge of system design. It is ideal for those addressing complex design problems at work or simply interested in enhancing their skills with low-level programming in general. A foundational understanding of programming concepts and experience with at least one programming language is required.
Chapter 1, Why Go?, provides an overview of Go’s suitability for building efficient and high-performance system software, providing you with the necessary knowledge and skills to leverage Go’s capabilities for system-level development. The chapter covers Go’s concurrency model, networking and I/O, low-level control, system calls, cross-platform support, and tooling, offering practical insights and examples for building robust system programs.
Chapter 2, Refreshing Concurrency and Parallelism, provides an overview of the core aspects of Goroutines, data races, channels, and their interplay in the Go programming language. Understanding these principles is critical for implementing efficient concurrency, managing shared resources, and ensuring effective inter-goroutine communication.
Chapter 3, Understanding System Calls, provides an overview of system calls and their practical applications. You will learn how to create symbolic links, unlink files, and manipulate filename paths. Also, you will better understand package OS and syscall in Go and learn how to develop and test a CLI program.
Chapter 4, File and Directory Operations, provides an overview of handling filesystems in Go, focusing on detecting unsafe permissions, calculating directory sizes, and identifying duplicate files.
Chapter 5, Working with System Events, provides comprehensive insights into building advanced and efficient system tools using Go, focusing on task scheduling, file monitoring, process management, and distributed locking.
Chapter 6, Understanding Pipes in Inter-Process Communication, provides an exploration of the concept of pipes in Inter-Process Communication (IPC). It provides comprehensive information about anonymous pipes, named pipes (mkfifo), and how pipes interact with other programs.
Chapter 7, Unix Sockets, provides an understanding of how UNIX sockets function, their types, and their role in IPC on UNIX and UNIX-like operating systems such as Linux.
Chapter 8, Memory Management, focuses on the mechanisms and strategies underpinning garbage collection. We’ll explore the evolution of Go’s garbage collection, the distinctions between stack and heap memory allocations, and advanced techniques for efficient memory management.
Chapter 9, Analyzing Performance, covers key optimization techniques for Go applications, including escape analysis, benchmarking, CPU profiling, and memory profiling. It explains how to improve memory usage with escape analysis, measure and compare code performance through benchmarking, identify hotspots with CPU profiling, and detect memory leaks using memory profiling.
Chapter 10, Networking, delves into the fascinating world of Go network programming. Networking is essential to system programming, and Go provides powerful primitives for handling network communications. You will gain the expertise needed to create robust networked applications by exploring TCP, HTTP, and additional relevant protocols.
Chapter 11, Telemetry, dives into how you can leverage industry tools to implement effective telemetry practices. From logs to traces and metrics, you’ll explore the tools and guidelines necessary to monitor your application efficiently.
Chapter 12, Distributing Your Apps, explores the key concepts and practical applications of distributing applications using Go modules, continuous integration, and release strategies.
Chapter 13, Capstone Project - Distributed Cache, guides you through the Capstone Project. This project will build a distributed cache system in Go with features such as Memcached or Redis. It will cover sharding strategies, eviction policies, consistency models, and technology choices, all while navigating the trade-offs that come with each decision.
Chapter 14, Effective Coding Practices, explores the principles and techniques of efficient resource management in Go programming, specifically focusing on avoiding common pitfalls that can lead to performance issues and hinder overall efficiency. It dives into the intricacies of optimizing resource usage using the Go standard library, providing strategies for developers seeking to enhance the effectiveness of their Go applications.
Chapter 15, Stay Sharp with System Programming, provides a continuous learning path to Go-based system programming based on real-world case studies. By gaining insight into how Go is utilized in actual applications, you can apply these lessons to their projects.
Appendix, Hardware Automation, explores how to utilize various tools to automate mundane tasks with USB drives and Bluetooth devices and monitor peripheral events. By understanding how to automate these processes, you will save valuable time and increase productivity in your daily lives.
You will need to have an understanding of the basics of Golang.
Software
Operating system requirements
Golang (1.16+)
Windows, macOS, or Linux (preferably Linux)
If you are using the digital version of this book, we advise you to type the code yourself or access the code from the book’s GitHub repository (a link is available in the next section). Doing so will help you avoid any potential errors related to the copying and pasting of code.
You can download the example code files for this book from GitHub at https://github.com/PacktPublishing/System-Programming-Essentials-with-Go. If there’s an update to the code, it will be updated in the GitHub repository.
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
There are a number of text conventions used throughout this book.
Code in text: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: “Lastly, update the main function to create a cache with a specified capacity and test the TTL and LRU features.”
A block of code is set as follows:
func main() { cache := NewCache(5) // Setting capacity to 5 for LRU cache.startEvictionTicker(1 * time.Minute) }Any command-line input or output is written as follows:
go run main.go -port=:8080 -peers=http://localhost:8081Tips or important notes
Appear like this.
Feedback from our readers is always welcome.
General feedback: If you have questions about any aspect of this book, email us at [email protected] and mention the book title in the subject of your message.
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/support/errata and fill in the form.
Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.
Once you’ve read System Programming Essentials with Go, we’d love to hear your thoughts! Please click here to go straight to the Amazon review page for this book and share your feedback.
Your review is important to us and the tech community and will help us make sure we’re delivering excellent quality content.
Thanks for purchasing this book!
Do you like to read on the go but are unable to carry your print books everywhere?
Is your eBook purchase not compatible with the device of your choice?
Don’t worry, now with every Packt book you get a DRM-free PDF version of that book at no cost.
Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical books directly into your application.
The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free content in your inbox daily
Follow these simple steps to get the benefits:
Scan the QR code or visit the link belowhttps://packt.link/free-ebook/9781837634132
Submit your proof of purchaseThat’s it! We’ll send your free PDF and other benefits to your email directlyIn this part, we will explore the foundational aspects of using Go for system programming. You will learn about the best practices in managing concurrency, and ensuring efficient cross-platform development. This section provides a closer look at why Go is a powerful choice for building high-performance system software and how to leverage its features to support real-world scenarios.
This part has the following chapters:
Chapter 1, Why Go?Chapter 2, Refreshing Concurrency and ParallelismAt some point in your programming journey, your programs performed I/O-related tasks such as creating and removing files and directories. They may have orchestrated the creation of new processes and the execution of other programs or even facilitated communication between threads and processes on the same computer and between processes on different computers connected via a network.
When our programs center on using a low-level set of tasks, we categorize them as system programming.
It is alleged that system programming is tedious. But I do not see it this way at all! In fact, it is quite the opposite – an enjoyable and entertaining experience. It is like being a magician. You get to control the operating system and hardware, and you can make things happen that would be impossible in other languages.
In this chapter, we discuss why Go is an excellent fit for building efficient, high-performance system software to support real-world scenarios.
In this chapter, we are going to cover the following main topics:
Choosing GoConcurrency and goroutinesInteracting with the OSToolingCross-platform development with GoBy the end of this chapter, you will have a grasp of where Go is in the ecosystem of system programming, the importance of the Go concurrency model to build efficient and high-performance system software, how Go chooses to interact with the OS, the Go approach to cross-platform development, and the main commands in the Go built-in tooling.
There are plenty of languages in the system programming space nowadays: some are well established, such as C and C++; some form a wave of newcomers, such as Zig, Rust, and Odin; and others claim the title of “C/C++ killer,” with their pledges of impressive performance.
Sure, we can use all of them and achieve outstanding results. Still, we could fall into hidden traps such as a steep learning curve, high cognitive load, a lack of community and support, inconsistent APIs with constant breaking changes, and a lack of adoption.
Go’s design philosophy emphasizes simplicity, expressiveness, robustness, and efficiency. Its support for concurrency and strong dependency management, as well as its focus on composition, make it a compelling choice for system programming. Its creators aimed to build a language that provides powerful building blocks without unnecessary complexity, which makes writing, reading, understanding, and maintaining system-level code easier. People with programming experience usually take two weeks to get acquainted with Go. While they may not be considered experts, they can confidently read standard Go code and write basic to medium-complexity programs without struggle.
Also, Go is excellent for system programming because the language has a Unix-minded design by checking all the boxes for simplicity. Many programmers who are proficient in Python and Ruby often transition to Go, as it allows them to retain their level of expressiveness while achieving improved performance and the capability to work with concurrency.
It is worth noting that Go’s philosophy doesn’t prioritize zero cost in terms of CPU usage. Instead, the language aims to reduce the effort demanded from programmers, which is considered more significant and, as a by-product, makes the experience enjoyable.
One of the leading criticisms of using Go for system programming is the garbage collector (GC), specifically its pauses and explicit memory limits. If you still have this pet peeve with Go, don’t worry. In Chapter 6, we’ll see that more granular memory management is available from Go 1.20 and above.
Note
In a GC pause worst-case scenario, the stop-the-world time is typically less than 100 microseconds.
One of the most essential features of Go is its concurrency model. Concurrency is the ability to run multiple tasks at the same time. In system programming, executing many tasks in parallel is essential for improving the performance and responsiveness of our programs.
Real-time systems demand precision, with concurrency being a pivotal factor. These systems coordinate tasks with exceptional timing, particularly in scenarios where even milliseconds matter. Concurrency offers significant advantages by increasing throughput (a measure of how many units of information a system can process in a given amount of time) while decreasing task completion times. Real-life instances show how concurrency improves responsiveness, making systems more flexible and tasks more efficient. Moreover, concurrency’s isolation abilities guarantee data integrity by preventing interference.
System programming involves a diverse range of tasks, from CPU-bound to I/O-bound. Concurrency orchestrates this diversity by allowing CPU-bound tasks to progress while I/O-bound tasks await resources.
Later, in Chapter 10, when we discuss distributed systems, the importance of concurrency will shine. It orchestrates tasks across an application or even different nodes in the network, which is ideal for managing large-scale concurrency.
Go’s concurrency model relies on goroutines and channels. Goroutines are lightweight execution threads, often referred to as green threads. Creating them is cost-effective. Unlike conventional threads, they exhibit remarkable efficiency, enabling thousands of goroutines to run simultaneously on just a few OS threads.
Channels, on the other hand, provide a mechanism for goroutines to communicate and synchronize without resorting to locks. This approach is inspired by the Communicating Sequential Process (CSP) (https://www.cs.cmu.edu/~crary/819-f09/Hoare78.pdf) formalism, emphasizing coordinated interactions between concurrent components.
Diverging from numerous other programming languages that depend on external libraries or threading constructs for concurrency, Go incorporates concurrency seamlessly into its core language design. This design decision leads to code that is not only easier to comprehend but also less susceptible to errors, as the complexities of threading are abstracted.
Go’s concurrency model draws inspiration from CSP, a formal language for describing concurrent systems. CSP focuses on communication and synchronization between concurrently executing entities. Unlike traditional multi-threaded programming, CSP and Go prioritize communication through channels instead of shared memory, reducing complexity and potential hazards. Synchronization and coordination are essential, with CSP using channels for process synchronization and Go using similar channels to coordinate goroutines. Safety and isolation are key, as both languages ensure safe interaction through channels, enhancing predictability and reliability. Go’s channels directly realize CSP’s communication-based approach, providing a safe way for goroutines to exchange data without the pitfalls of shared memory and locks.
The famous Go proverb, “Don’t communicate by sharing memory, share memory by communicating” is often a source of discussion and misinterpretation. Still, reading it as “Share by communicating, not by locking” would be more precise, mainly because mainstream languages often rely on locks to protect shared data, leading to potential issues such as deadlocks and race conditions. Go encourages a different paradigm: sharing data through channels by sending and receiving messages. This “share by communicating” philosophy reduces the need for explicit locks and promotes a safer concurrency environment.
If you are into functional programming, I have great news for you. In Go, data is not implicitly shared between goroutines. In other words, the data is copied. Did you see the concern with data immutability? This stands in contrast to languages, where shared memory is the default mode of communication between threads. Go’s emphasis on explicit communication via channels helps avoid the unintended data sharing and race conditions that can arise in traditional threading models. Another benefit of this model is that there is no callback hell since every interaction with concurrent code is often read in a procedural manner. A regular Go function can be used in procedural code without tying the signatures with extra keywords.
Note
Callback hell, also known as the “pyramid of doom,” is a term used in programming to describe a situation where nested and interdependent callback functions make the code difficult to read, understand, and maintain. This typically occurs in asynchronous programming environments, such as JavaScript, where callbacks are used to handle asynchronous operations.
In the next chapter, we will refresh all concepts of concurrency and its building blocks to prepare you for interacting with the OS interfaces.
In addition to its concurrency model, Go also provides a way to interact with the operating system at a low level. This is essential for system programming, where you often need to control the OS and hardware.
Go’s approach to system calls is designed to be safe and efficient, especially in the context of its concurrency model.
In Go, system calls are comparatively lower-level in comparison to certain other programming languages. It can be helpful if you need fine-grained control over system resources, but it also means you’re dealing with more low-level details.
Making system calls often requires understanding the underlying operating system APIs and conventions. The side effect is that it can introduce a steeper learning curve if you are new to systems programming or lower-level development.
Not familiar with system calls? Fear not! The book’s second part will explore and experiment with them in detail to cover the main aspects we need to progress in our system programming journey.
Go is like a toolbox. It has everything we need to build great software, so we don’t need anything more than its standard tools to create our programs.
Let’s explore the principal tools that facilitate building, testing, running, error-checking, and code formatting.
The go build command is used to compile Go code into an executable binary that you can run.
Let’s see an example.
Assume you have a Go source file named main.go containing the following code:
package main import "fmt" func main() { fmt.Println("Hello, Go!") }You can compile it using the go build command:
go build main.goThis will generate an executable binary named main (or main.exe on Windows). You can then run the binary to see the output:
./mainThe go test command is used to run tests on your Go code. It automatically finds test files and runs the associated test functions.
Here’s an example.
Assume you have a Go source file named math.go containing a function to add two numbers:
package math func Add(a, b int) int { return a + b }You can create a test file named math_test.go to write tests for the Add function:
package math import "testing" func TestAdd(t *testing.T) { result := Add(2, 3) if result != 5 { t.Errorf("Expected 5, but got %d", result) } }Run the tests using the go test command:
go testThe go run command allows you to run Go code directly without explicitly compiling it into an executable.
Let’s see this using an example.
Assume you have a Go source file named hello.go containing the following code:
package main import "fmt" func main() { fmt.Println("Hello, Go!") }You can directly run the code using the go run command:
go run hello.goThis will execute the code and print Hello, Go! to the console.
We use the go vet command to check our Go code for potential errors or suspicious constructs. It employs heuristics that may not ensure all reports are actual issues, but it can uncover errors not caught by the compilers.
Here’s an example.
Assume you have a Go source file named error.go containing the following code with an intentional error:
package main import "fmt" func main() { movie_year := 1999 movie_title := "The Matrix" fmt.Printf("In %s, %s was released.\n", movie_year, movie_title) }You can use the go vet command to check for errors:
go vet error.goIt might report a warning such as this: Printf format %s has arg 1999 of wrong type int.
The go fmt command is used to format your Go code according to the Go programming style guidelines. It automatically adjusts code indentation, spacing, and more.
Let’s see an example for this too.
Assume you have a Go source file named unformatted.go containing improperly formatted code:
package main import "fmt" func main() { msg:="Hello" fmt.Println(msg) }You can format the code using the go fmt command:
go fmt unformatted.goIt will update the code to match the standard formatting conventions:
package main import "fmt" func main() { msg := "Hello" fmt.Println(msg) }Now that we have a good grasp of the basic tools, we can start familiarizing ourselves with Go’s cross-platform capabilities.
Cross-platform development with Go is a breeze. You can write code running on various operating systems and architectures with ease.
Cross-platform development with Go can be achieved by using the GOOS and GOARCH environment variables. The GOOS environment variable specifies the OS you want to target, and the GOARCH environment variable specifies your target architecture.
For example, assume you have a Go source file named main.go:
package main import "fmt" func main() { fmt.Println("This program runs in any OS!") }To compile code for Linux, you would set the GOOS environment variable to linux and the GOARCH environment variable to amd64:
GOOS=linux GOARCH=amd64 go buildThis command will compile the code for Linux.
You can also use the GOOS and GOARCH environment variables to run code on different platforms. For example, to run the code you compiled for Linux on macOS, you would set the GOOS environment variable to darwin and the GOARCH environment variable to amd64.
GOOS=darwin GOARCH=amd64 go runThis command will run the code on macOS.
Note
Although Go strives for portability across various platforms, engaging with the OS via system calls inherently ties your code to specific OS features. Code that is heavily reliant on these operations might need conditional compilation or adjustments when aiming at different platforms.
Leveraging build flags in Go allows you to selectively compile specific sections of your code contingent upon particular conditions, such as the target OS or architecture.
This can be useful when creating programs that interface with the golang.org/x/sys package for Windows and Unix-like systems.
Assume that you have two Go source files named main_windows.go and main_linux.go and you want to use build tags to ensure code segmentation.
Here is an example of a code using build tags to segment for Windows:
// go:build windows package main import "fmt" func main() { fmt.Println("This is Windows!") }We can do the same but aiming for Linux this time:
// go:build linux package main import "fmt" func main() { fmt.Println("This is Linux!") }These are the commands to use to compile these programs, respectively:
GOOS=windows go build -o app.exe GOOS=linux go build -o appWhen we execute app within a Linux environment, it should print This is Linux!. Meanwhile, on a Windows system, running app.exe will display This is Windows!.
This chapter provided a comprehensive guide to why Go is a top choice for system programming and insights into Go’s design philosophy, emphasizing simplicity, robustness, and efficiency. We learned about Go’s concurrency model, its approach to interacting with the OS, and how to interact with the tools for cross-platform development. These lessons are helpful as they equip us with the knowledge needed to write, read, and maintain system-level code with Go, enabling improved performance and the ability to work with concurrency.
In the next chapter, we explore the concurrency concepts, refreshing all related concepts and building blocks. It will prepare us for more advanced interactions with OS interfaces, enhancing our ability to create powerful and responsive programs.