Skip to main content

Write Your First Example

In this example, we will demonstrate a simple example using various Photon modules, i.e., common, thread, fs, io and net. The program basically set up some Photon threads in the background, created a Photon file for IO, and sent buffer through Photon socket. Photon locks and condition viariables are used as well.

note

The example code is written in std-compatible API.

If you want to use the raw API, please refer to this doc. It can provide more flexible functionalities.

1. Initialize environment

#include <photon/photon.h>
#include <photon/thread/std-compat.h>

int main() {
int ret = photon::init(photon::INIT_EVENT_DEFAULT, photon::INIT_IO_NONE);
if (ret != 0) {
return -1;
}
DEFER(photon::fini());
// ...
}

After photon::init, the Env is initialized, which means the coroutine stack is successfully allocated on current vCPU.

Now you can create multiple Photon threads to run in parallel, or migrate them to other vCPUs.

The photon::fini is responsible for deallocating the environment. It's wrapped in a helper macro called DEFER from common/utility.h. Like Go's defer, it ensures the statement be executed before the function returns. Its implementation is based on the concept of RAII.

2. Create thread

There are many ways to create a thread. Just like the old ways you use std::thread, but try photon_std::thread instead.

// Global function
int func(int a, char* b) {}

// Use global function to create a thread.
// Will be automatically joined when thread object destructed, unless been detached.
photon_std::thread th(func, 1, '2');
th.detach();

// Create a thread with anonymous function (lambda)
photon_std::thread th([&] {
// Access variables directly in the context
});

// Create a thread with class member function
class A {
void f() {
new photon_std::thread(&A::g, this, 1, '2');
}
void g(int a, char* b) {}
};

3. Concurrency

A thread is basically a function, and thus an execution unit. You can create multiple threads at a time to achieve concurrency, and wait them finished by Join.

std::vector<photon_std::thread> threads;
for (int i = 0; i < 100; ++i) {
threads.emplace_back(func, 1, '2');
}
for (auth& th : threads) {
th.join();
}

4. Lock and synchronization

This is a typical condition_variable usage. Again, we switch to Photon's exclusive namespace.

bool condition = false;
photon_std::mutex mu;
photon_std::condition_variable cv;

// Consumer thread
photon_std::thread([&]{
auto timeout = std::chrono::duration<std::chrono::seconds>(10);
photon_std::unique_lock<photon_std::mutex> lock(mu);
while (!condition) {
cv.wait(lock, timeout);
}
}).detach();

// Producer thread
photon_std::thread([&]{
photon_std::lock_guard<photon_std::mutex> lock(mu);
condition = true;
cv.notify_one();
}).detach();

5. File IO

Photon has POSIX-like encapsulations for file and filesystem. In this example we first create a IFileSystem under current working dir, and then open a IFile from it.

You can switch the io_engine from photon::fs::ioengine_psync to photon::fs::ioengine_iouring, if your event_engine satisfied. io_uring IO is naturally asynchronous and non-blocking. It can also use page cache compared with aio.

In addition to local file systems, Photon also supports a variety of remote file systems, such as httpfs, extfs, fusefs, etc.

#include <photon/fs/localfs.h>

auto fs = photon::fs::new_localfs_adaptor(".", photon::fs::ioengine_psync);
if (!fs) {
LOG_ERRNO_RETURN(0, -1, "failed to create fs");
}
DEFER(delete fs);

auto file = fs->open("test-file", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (!file) {
LOG_ERRNO_RETURN(0, -1, "failed to open file");
}
DEFER(delete file);

ssize_t n_written = file->write(buf, 4096);

Both IFile and IFileSystem object will close itself at destruction. Again, RAII.

6. Socket

The tcp_socket_client + tcp_socket_server is the most regular combination for client and server. Please refer to the API docs for more socket types.

Client

#include <photon/net/socket.h>

auto client = photon::net::new_tcp_socket_client();
if (client == nullptr) {
LOG_ERRNO_RETURN(0, -1, "failed to create tcp client");
}
DEFER(delete client);

photon::net::EndPoint ep("127.0.0.1:9527");
auto stream = client->connect(ep);
if (!stream) {
LOG_ERRNO_RETURN(0, -1, "failed to connect server");
}
DEFER(delete stream);

// Send data to socket
char buf[1024];
if (stream->send(buf, 1024) != 1024) {
LOG_ERRNO_RETURN(0, -1, "failed to write socket");
}

Server

#include <photon/net/socket.h>

auto server = photon::net::new_tcp_socket_server();
if (server == nullptr) {
LOG_ERRNO_RETURN(0, -1, "failed to create tcp server");
}
DEFER(delete server);

auto handler = [&](photon::net::ISocketStream* stream) -> int {
char buf[1024];
ssize_t ret = stream->recv(buf, 1024);
if (ret <= 0) {
LOG_ERRNO_RETURN(0, -1, "failed to read socket");
}
return 0;
};

server->set_handler(handler);
server->bind_v4localhost(9527);
server->listen();

LOG_INFO("Server is listening for port ` ...", 9527);
server->start_loop(true);
info

The stream is of type photon::net::ISocketStream. It has extended write/read methods compared to traditional libc's send/recv.

Essentially, write = fully_send, and read = fully_recv.

info

LOG_INFO is Photon's unique logging system. It is based on template metaprogramming techniques and optimizes results at compile time. So runtime overhead is reduced. Compared with other logging systems based on sprintf, Photon logging is 2~3 times faster than them.

The ` symbol is a generic placeholder for multiple types of elements.

Full source code

You may visit https://github.com/alibaba/PhotonLibOS/blob/main/examples/simple/simple.cpp to get the source.

Or create your own CMake demo project guided by the How to integrate doc.