From the translator: This article is the seventh in the translation cycle of the official SFML library guide. Past article can be found here. This series of articles aims to provide people who do not know the original language the opportunity to get acquainted with this library. SFML is a simple and cross-platform multimedia library. SFML provides a simple interface for developing games and other multimedia applications. The original article can be found here . Let's start.What is a thread?
Most of you already know what a stream is, but we will explain what it is for newcomers to this topic.
A thread is essentially a sequence of instructions that are executed in parallel with other threads. Each program creates at least one thread: the main one, which runs the main () function. A program that uses only the main thread is single-threaded; if you add one or more threads, it becomes multi-threaded.
So, in short, threads are a way to do several things at once. This can be useful, for example, for displaying animations and processing user input while loading images or sounds. Flows are also widely used in network programming, while waiting for data to be received, the application will continue to be updated and drawn.
SFML or std :: thread threads?
In its latest version (2011), the standard C ++ library provides a
set of classes for working with streams . At the time of writing SFML, the C ++ 11 standard was not yet written and there was no standard way to create threads. When SFML 2.0 was released, there were many compilers that did not support this new standard.
If you are working with a compiler that supports the new standard and contains a header file, forget about the SFML stream classes and use the standard C ++ classes instead. But, if you work with a compiler that does not support this standard, or plan to distribute your code and want to achieve full portability, the SFML stream classes are a good choice.
Creating streams using SFML
Enough talk, let's look at the code. The class that makes it possible to create threads using SFML is called
sf :: Thread , and this is how (thread creation) looks like in action:
#include <SFML/System.hpp> #include <iostream> void func() {
In this code, the functions main and func are executed in parallel after calling thread.launch (). The result of this is that the text displayed by both functions is mixed in the console.

The entry point into the stream, i.e. the function that will be executed when the thread starts must be passed to the
sf :: Thread constructor.
sf :: Thread tries to be flexible and accept various entry points: non-member functions or class methods, functions with or without arguments, functors, and so on. The above example shows how to use a member function, here are a few other examples.
- non-member function with one argument:
void func(int x) { } sf::Thread thread(&func, 5);
- class method:
class MyClass { public: void func() { } }; MyClass object; sf::Thread thread(&MyClass::func, &object);
- functor (functional object):
struct MyFunctor { void operator()() { } }; sf::Thread thread(MyFunctor());
The last example that the functor uses is the most powerful because it can accept any type of functor and therefore makes the
sf :: Thread class compatible with many types of functions that are not directly supported. This function is especially interesting with C ++ 11 lambda expressions or std :: bind.
// std::bind void func(std::string, int, double) { } sf::Thread thread(std::bind(&func, "hello", 24, 0.5));
If you want to use
sf :: Thread inside a class, do not forget that it does not have a standard constructor. Therefore, you must initialize it in the constructor of your class in the initialization list:
class ClassWithThread { public: ClassWithThread() : m_thread(&ClassWithThread::f, this) { } private: void f() { ... } sf::Thread m_thread; };
If you really need to create an instance of
sf :: Thread after initializing an object, you can create it in a heap.
Start stream
After you have created an instance of
sf :: Thread , you must start it by running the function.
sf::Thread thread(&func); thread.launch();
A launch invokes a function that you passed to the constructor of a new thread, and immediately ends its work, so that the calling thread can immediately continue execution.
Stop threads
The thread automatically terminates when the function that serves as the entry point for the stream returns its value. If you want to wait for the completion of a thread from another thread, you can call its wait function.
sf::Thread thread(&func); // thread.launch(); ...
The wait function is also implicitly called by the
sf :: Thread destructor, so the thread cannot remain started (and uncontrolled) after its
sf :: Thread instance is destroyed. Remember this when you manage your streams (see the last section of the article).
Pause flow
There is no function in SFML that provides a way to pause a thread; The only way to pause a thread is to do it from the code of the thread itself. In other words, you can only pause the current thread. To do this, you can call the sf :: sleep function:
void func() { ... sf::sleep(sf::milliseconds(10)); ... }
sf :: sleep has one argument — the pause time. This time can be expressed in any unit, as was shown in the article about
processing time .
Note that you can pause any thread with this function, even the main thread.
sf :: sleep is the most effective way to suspend a thread: during the suspension of the thread, it (the stream) consumes almost no CPU resources. Suspending, based on active waiting, like an empty while loop, consumes 100% of CPU resources and does ... nothing. However, keep in mind that the duration of the suspension is just a hint; the actual duration of the suspension (more or less than the time you specify) depends on the OS. So do not rely on this feature with a very accurate countdown.
Shared Data Protection
All threads in the program share some memory, they have access to all variables in their scope. This is very convenient, but also dangerous: since the parallel start of a thread, variables or functions can be used simultaneously by different threads. If the operation is not thread safe, it can lead to undefined behavior (i.e., it can lead to data failure or corruption).
There are several software tools that can help you protect shared data and make your code thread-safe, they are called synchronization primitives. The most common are the following primitives: mutexes, semaphores, conditional variables, and spinlocks. All of them are variants of the same concept: they protect a piece of code, giving only a certain stream the right to access data and block others.
The most common (and used) primitive is a mutex. Mutex stands for Mutual Exclusion. This is a guarantee that only one thread can execute code. Let's see how mutexes work, using the example below:
#include <SFML/System.hpp> #include <iostream> sf::Mutex mutex; void func() { mutex.lock(); for (int i = 0; i < 10; ++i) std::cout << "I'm thread number one" << std::endl; mutex.unlock(); } int main() { sf::Thread thread(&func); thread.launch(); mutex.lock(); for (int i = 0; i < 10; ++i) std::cout << "I'm the main thread" << std::endl; mutex.unlock(); return 0; }
This code uses a shared resource (std :: cout), and, as we can see, this leads to undesirable results. Output streams mixed in console. To ensure that the output prints correctly, instead of being randomly mixed, we protect the relevant areas of the code with a mutex.
The first thread that reaches the mutex.lock () call blocks the mutex and gains access to the code that prints the text. When other threads reach the mutex.lock () call, the mutex is already locked, and other threads suspend their execution (this is similar to the sf :: sleep call, the sleep thread does not consume CPU time). When the first thread unlocks the mutex, the second thread continues its execution, locks the mutex and prints the text. This leads to the fact that the text in the console is printed sequentially and does not mix.

A mutex is not only a primitive that you can use to protect shared data, you can use it in many other cases. However, if your application does complex things when working with threads, and you feel that the capabilities of mutexes are not enough - do not hesitate to look for another library with more functionality.
Mutex Protection
Do not worry: mutexes are already thread-safe, there is no need to protect them. But they are not safe in terms of exceptions. What happens if an exception is thrown when the mutex is locked? It can never be unlocked and will remain locked forever. All threads trying to unlock a locked mutex will be blocked forever. In some cases, your application will be "frozen."
To ensure that a mutex is always unlocked in an environment in which it (the mutex) can throw an exception, SFML provides a RAII class that allows you to wrap the mutex into the sf :: Lock class. The lock occurs in the constructor, unlocking occurs in the destructor. Simple and effective.
sf::Mutex mutex; void func() { sf::Lock lock(mutex);
Remember that sf :: Lock can also be used in functions that have multiple return values.
sf::Mutex mutex; bool func() { sf::Lock lock(mutex); // mutex.lock() if (!image1.loadFromFile("...")) return false; // mutex.unlock() if (!image2.loadFromFile("...")) return false; // mutex.unlock() if (!image3.loadFromFile("...")) return false; // mutex.unlock() return true; } // mutex.unlock()
Common Misconceptions
A thing that is often overlooked: a thread cannot exist without a corresponding
sf :: Thread instance. The following code can often be seen on the forums:
void startThread() { sf::Thread thread(&funcToRunInThread); thread.launch(); } int main() { startThread();
Programmers who write such code expect the startThread () function to start a thread that will live on its own and be destroyed when the function completes (passed as an entry point). This is not happening. The thread function blocks the main thread, as if the program were not running.
What's the matter? The
sf :: Thread instance is local to the startThread () function, so it is immediately destroyed when the function returns its value. The
sf :: Thread destructor is called, wait () is called, as stated above, the result is a blocking of the main thread, which waits for the thread function to complete, instead of executing it in parallel.
So do not forget: you must manage the
sf :: Thread instances so that they live as long as the thread functions are required.
Next article:
Working with custom data streams .