Я использовал fork() в C, чтобы начать другой процесс. Как начать новый поток?
Как начать потоки в plain C?
Ответ 1
Поскольку вы упомянули fork(), я предполагаю, что вы используете Unix-подобную систему, и в этом случае потоки POSIX (обычно называемые pthreads) - это то, что вы хотите использовать.
В частности, pthread_create() - это функция, необходимая для создания нового потока. Его аргументы:
int pthread_create(pthread_t * thread, pthread_attr_t * attr, void *
(*start_routine)(void *), void * arg);
Первый аргумент - это возвращаемый указатель на идентификатор потока. Второй аргумент - это аргументы потока, которые могут быть NULL, если вы не хотите запускать поток с определенным приоритетом. Третий аргумент - это функция, выполняемая потоком. Четвертый аргумент - это единственный аргумент, переданный функции потока при его выполнении.
Ответ 2
AFAIK, ANSI C не определяет потоки, но существуют различные библиотеки.
Если вы работаете в Windows, подключитесь к msvcrt и используйте _beginthread или _beginthreadex.
Если вы работаете на других платформах, проверьте библиотеку pthreads (я уверен, что есть и другие).
Ответ 3
Нити не являются частью стандарта C, поэтому единственный способ использовать потоки - использовать некоторую библиотеку (например: потоки POSIX в Unix/Linux, _beginthread/_beginthreadex, если вы хотите использовать C-runtime из этого потока или просто CreateThread Win32 API)
Ответ 4
pthreads - хорошее начало, посмотрите здесь
Ответ 5
Посмотрите pthread (библиотека потоков POSIX).
Ответ 6
Резьба C11 + C11 atomic_int
Добавлено в glibc 2.28. Протестировано в Ubuntu 18.10 amd64 (поставляется с glic 2.28) и Ubuntu 18.04 (поставляется с glibc 2.27) путем компиляции glibc 2.28 из источника: Несколько библиотек glibc на одном хосте
Пример адаптирован из: https://en.cppreference.com/w/c/language/atomic
main.c
#include <stdio.h>
#include <threads.h>
#include <stdatomic.h>
atomic_int atomic_counter;
int non_atomic_counter;
int mythread(void* thr_data) {
(void)thr_data;
for(int n = 0; n < 1000; ++n) {
++non_atomic_counter;
++atomic_counter;
// for this example, relaxed memory order is sufficient, e.g.
// atomic_fetch_add_explicit(&atomic_counter, 1, memory_order_relaxed);
}
return 0;
}
int main(void) {
thrd_t thr[10];
for(int n = 0; n < 10; ++n)
thrd_create(&thr[n], mythread, NULL);
for(int n = 0; n < 10; ++n)
thrd_join(thr[n], NULL);
printf("atomic %d\n", atomic_counter);
printf("non-atomic %d\n", non_atomic_counter);
}
Скомпилируйте и запустите:
gcc -ggdb3 -std=c11 -Wall -Wextra -pedantic -o main.out main.c -pthread
./main.out
Возможный вывод:
atomic 10000
non-atomic 4341
Скорее всего, неатомарный счетчик будет меньше атомного из-за быстрого доступа через потоки к неатомарной переменной.
См. также: Как сделать атомарный инкремент и извлечь в C?
Анализ разборки
Разберите с помощью:
gdb -batch -ex "disassemble/rs mythread" main.out
содержит:
17 ++non_atomic_counter;
0x00000000004007e8 <+8>: 83 05 65 08 20 00 01 addl $0x1,0x200865(%rip) # 0x601054 <non_atomic_counter>
18 __atomic_fetch_add(&atomic_counter, 1, __ATOMIC_SEQ_CST);
0x00000000004007ef <+15>: f0 83 05 61 08 20 00 01 lock addl $0x1,0x200861(%rip) # 0x601058 <atomic_counter>
поэтому мы видим, что атомарный инкремент выполняется на уровне команд с префиксом блокировки f0
.
С aarch64-linux-gnu-gcc
8.2.0 мы получаем вместо:
11 ++non_atomic_counter;
0x0000000000000a28 <+24>: 60 00 40 b9 ldr w0, [x3]
0x0000000000000a2c <+28>: 00 04 00 11 add w0, w0, #0x1
0x0000000000000a30 <+32>: 60 00 00 b9 str w0, [x3]
12 ++atomic_counter;
0x0000000000000a34 <+36>: 40 fc 5f 88 ldaxr w0, [x2]
0x0000000000000a38 <+40>: 00 04 00 11 add w0, w0, #0x1
0x0000000000000a3c <+44>: 40 fc 04 88 stlxr w4, w0, [x2]
0x0000000000000a40 <+48>: a4 ff ff 35 cbnz w4, 0xa34 <mythread+36>
поэтому в атомарной версии фактически есть цикл cbnz
, который выполняется до тех пор, пока хранилище stlxr
не будет успешным.
Benchmark
СДЕЛАТЬ. Создайте эталонный тест, чтобы показать, что атомная медленнее.
POSIX темы
main.c
#define _XOPEN_SOURCE 700
#include <assert.h>
#include <stdlib.h>
#include <pthread.h>
enum CONSTANTS {
NUM_THREADS = 1000,
NUM_ITERS = 1000
};
int global = 0;
int fail = 0;
pthread_mutex_t main_thread_mutex = PTHREAD_MUTEX_INITIALIZER;
void* main_thread(void *arg) {
int i;
for (i = 0; i < NUM_ITERS; ++i) {
if (!fail)
pthread_mutex_lock(&main_thread_mutex);
global++;
if (!fail)
pthread_mutex_unlock(&main_thread_mutex);
}
return NULL;
}
int main(int argc, char **argv) {
pthread_t threads[NUM_THREADS];
int i;
fail = argc > 1;
for (i = 0; i < NUM_THREADS; ++i)
pthread_create(&threads[i], NULL, main_thread, NULL);
for (i = 0; i < NUM_THREADS; ++i)
pthread_join(threads[i], NULL);
assert(global == NUM_THREADS * NUM_ITERS);
return EXIT_SUCCESS;
}
Скомпилируйте и запустите:
gcc -std=c99 -Wall -Wextra -pedantic -o main.out main.c -pthread
./main.out
./main.out 1
Первый запуск работает нормально, второй отказывает из-за отсутствия синхронизации.
Кажется, не существует стандартизированных атомарных операций POSIX: Переносимые атомарные операции UNIX
Проверено на Ubuntu 18.04. GitHub upstream.
Встроенные модули GCC __atomic_*
Для тех, у кого нет C11, вы можете достичь атомарных приращений с расширениями __atomic_*
GCC.
main.c
#define _XOPEN_SOURCE 700
#include <pthread.h>
#include <stdatomic.h>
#include <stdio.h>
#include <stdlib.h>
enum Constants {
NUM_THREADS = 1000,
};
int atomic_counter;
int non_atomic_counter;
void* mythread(void *arg) {
(void)arg;
for (int n = 0; n < 1000; ++n) {
++non_atomic_counter;
__atomic_fetch_add(&atomic_counter, 1, __ATOMIC_SEQ_CST);
}
return NULL;
}
int main(void) {
int i;
pthread_t threads[NUM_THREADS];
for (i = 0; i < NUM_THREADS; ++i)
pthread_create(&threads[i], NULL, mythread, NULL);
for (i = 0; i < NUM_THREADS; ++i)
pthread_join(threads[i], NULL);
printf("atomic %d\n", atomic_counter);
printf("non-atomic %d\n", non_atomic_counter);
}
Скомпилируйте и запустите:
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c -pthread
./main.out
Вывод и сгенерированная сборка: так же, как в примере с "C11 threads".
Протестировано в Ubuntu 16.04 amd64, GCC 6.4.0.