关于多线程:如何在普通C语言中启动线程?

关于多线程:如何在普通C语言中启动线程?

How do I start threads in plain C?

我已经在C语言中使用fork()启动了另一个进程。 如何启动新线程?


自从您提到fork()以来,我假设您使用的是类似Unix的系统,在这种情况下,您要使用POSIX线程(通常称为pthread)。

具体来说,pthread_create()是创建新线程所需的函数。它的参数是:

1
2
int  pthread_create(pthread_t  *  thread, pthread_attr_t * attr, void *
   (*start_routine)(void *), void * arg);

第一个参数是返回的线程ID指针。第二个参数是线程参数,除非您想以特定优先级启动线程,否则它可以为NULL。第三个参数是线程执行的函数。第四个参数是执行时传递给线程函数的单个参数。


AFAIK,ANSI C没有定义线程,但是有各种可用的库。

如果您在Windows上运行,请链接到msvcrt并使用_beginthread或_beginthreadex。

如果您在其他平台上运行,请签出pthreads库(我确定还有其他)。


线程不是C标准的一部分,因此使用线程的唯一方法是使用某些库(例如:Unix / Linux中的POSIX线程,_beginthread / _beginthreadex,如果您想使用该线程中的C运行时或仅使用CreateThread Win32 API)


pthreads是一个好的开始,请看这里


C11线程+ C11 atomic_int

已添加到glibc 2.28。通过从源代码编译glibc 2.28,在Ubuntu 18.10 amd64(glic 2.28附带)和Ubuntu 18.04(glibc 2.27附带)中进行了测试:在单个主机上有多个glibc库

改编自以下示例:https://en.cppreference.com/w/c/language/atomic

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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
"
, atomic_counter);
    printf("non-atomic %d
"
, non_atomic_counter);
}

GitHub上游。

编译并运行:

1
2
gcc -ggdb3 -std=c11 -Wall -Wextra -pedantic -o main.out main.c -pthread
./main.out

可能的输出:

1
2
atomic     10000
non-atomic 4341

由于跨线程访问非原子变量,非原子计数器很可能小于原子计数器。

另请参阅:如何在C中进行原子增量和获取?

拆卸分析

拆卸:

1
gdb -batch -ex"disassemble/rs mythread" main.out

包含:

1
2
3
4
5
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

因此我们看到原子增量是在指令级别使用f0锁定前缀完成的。

使用aarch64-linux-gnu-gcc 8.2.0,我们得到的是:

1
2
3
4
5
6
7
8
9
10
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存储成功为止。请注意,ARMv8.1可以使用一条LDADD指令完成所有这些操作。

基准测试

去做。创建一个基准以显示原子速度较慢。

POSIX线程

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#define _XOPEN_SOURCE 700
#include
#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;
}

编译并运行:

1
2
3
gcc -std=c99 -Wall -Wextra -pedantic -o main.out main.c -pthread
./main.out
./main.out 1

第一次运行正常,第二次运行由于缺少同步而失败。

似乎没有POSIX标准化的原子操作:UNIX可移植原子操作

在Ubuntu 18.04上测试。 GitHub上游。

GCC __atomic_*内置

对于那些没有C11的对象,可以使用__atomic_* GCC扩展名实现原子增量。

main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#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
"
, atomic_counter);
    printf("non-atomic %d
"
, non_atomic_counter);
}

编译并运行:

1
2
gcc -ggdb3 -O3 -std=c99 -Wall -Wextra -pedantic -o main.out main.c -pthread
./main.out

输出和生成的程序集:与" C11线程"示例相同。

已在Ubuntu 16.04 amd64,GCC 6.4.0中测试。


检出pthread(POSIX线程)库。


推荐阅读

    linux的u盘启动命令?

    linux的u盘启动命令?,系统,电脑,工具,信息,软件,网上,名称,工作,设备,通用,从

    linux防火墙命令启动?

    linux防火墙命令启动?,系统,状态,管理,密码,服务,工具,防火墙,网络,信息,软

    linux线程查询命令?

    linux线程查询命令?,系统,第一,线程,命令,软件,名称,信息,进程,选项,方法,Lin

    linux退出启动命令行?

    linux退出启动命令行?,系统,状态,档案,平台,命令,环境,模式,终端,程序,编辑,l

    关闭启动linux的命令?

    关闭启动linux的命令?,服务,系统,命令,代码,手机,软件,密码,管理,信息,状态,l

    linux中启动服务命令?

    linux中启动服务命令?,服务,系统,命令,信息,工作,设备,网络,标准,名称,密码,l

    linux多线程下载命令?

    linux多线程下载命令?,软件,工具,平台,中心,系统,代理,网络,网站,手机,官方

    linux永久启动命令?

    linux永久启动命令?,系统,服务,密码,软件,工具,电脑,位置,环境,状态,发行,lin

    linux命令行启动软件?

    linux命令行启动软件?,系统,工具,软件,位置,密码,电脑,首页,代码,环境,地方,l

    linux断开线程命令?

    linux断开线程命令?,系统,状态,工作,代码,线程,入口,网络,管理,名称,命令,lin

    linux命令是什么语言?

    linux命令是什么语言?,系统,环境,代码,传播,管理,语言,操作系统,源码,自由,

    linux命令设置自启动?

    linux命令设置自启动?,服务,系统,信息,数字,检测,工具,状态,密码,跨行,脚本,l

    linuxu盘启动命令?

    linuxu盘启动命令?,系统,软件,工具,数据,设备,位置,盘中,电脑,第一,信息,在li

    linux中启动软件命令?

    linux中启动软件命令?,环境,软件,电脑,系统,工具,位置,代码,设备,时间,情况,

    调用函数命令linux?

    调用函数命令linux?,系统,管理,网络,通用,统一,观察,地址,代码,设备,地方,怎

    命令行添加linux启动?

    命令行添加linux启动?,系统,软件,工具,环境,初级,地址,发行,命令,目录,终端,l

    linux增加自启动命令?

    linux增加自启动命令?,服务,系统,信息,工具,软件,查询系统,状态,跨行,情况,

    linux上启动脚本命令?

    linux上启动脚本命令?,服务,状态,系统,代码,脚本,工作,周期性,命令,文件,方

    linux改语言命令行?

    linux改语言命令行?,系统,环境,工具,密码,概念,地方,软件,通信,管理,国际,lin

    linux命令行c语言?

    linux命令行c语言?,代码,系统,工具,环境,工作,保险,发行,命令,文件,终端,linu