做人呢,最紧要就系开心啦

Linux多线程5:如何设计多线程程序

703次阅读
没有评论

1 划分线程的典型原则

《The Art of Concurrency》第四章 Eight Simple Rules for Designing Multithread Applications.
Linux 多线程 5:如何设计多线程程序

(1)区分真正独立的计算任务

(2)尽可能地在最高级别并发

    for..// 并发
        for …
            for …

(3)尽早考虑多核个数

(4)尽可能利用已有的线程安全库
许多的库,例如 Intel Math Kernel Library(Intel MKL)和 Intel Integrated Performance Primitives (Intel IPP),提供了能更好的利用多核处理器的并行版本的函数。

(5)使用合适的多线程模型
有 pthread, OpenMP 等,另外一本书
https://maxim.int.ru/bookshelf/PthreadsProgram/htm/r_19.html
Chapter 2 - Designing Threaded Programs
讲解基于 pthread 实现的多线程模型,难得的是 1996 年提出的模型,现今依然适用;

(6)永远不要假设多线程的执行顺序

(7)尽可能使用线程本地数据,或者对特定的数据加锁
数据越独立,更容易降低锁粒度;

(8)敢于更换更易并行化的算法

并发:逻辑上的同时发出;
并行:物理上,实际同时执行;

以上规则,适合 CPU 型任务分解

补充:
(9)让 I / O 型任务与 CPU 计算型任务,同时运行而不是互相等待.

2 典型的多线程模型

2.1 老板 - 工人 (boss-worker) 模型, w/ o 线程池;

(1)一个 boss 线程负责分派工作(delegation),遇到具体任务,创建线程去干活;

main()
/* The boss */
{
forever {
    get a request
    switch request:
        case X: pthread_create(...taskX)
        case Y: pthread_create(...taskY)
}
}
taskX() /*Workers processing requests of type X*/
{
perform the task, synchronize as needed if accessing shared resources
done
}

taskY() /*Workers processing requests of type Y*/
{perform the task, synchronize as needed if accessing shared resources done}

(2)先创建好 n 个线程(对应 n 个 worker),遇到具体任务,放入队列,唤醒等待队列,准备好的线程干活;

main()
/* The boss */
{
    for the numnber of workers
        pthread_create(...poll_base)
    forever {
        get a request
        place request in work queue;
        signal sleeping threads that work is available
    }
}
poll_base()
{while(1) {
        sleep until awoken by boss
            dequeuea  work request
            switch (case)
                case request X: taskX()
                case request Y: taskY()}
}

2.2 PEER 模型:

没有 Boss 分派工作的过程,每个线程都知道自己要做什么,有自己有输入(workcrew)

main()
{pthread_create(...thread1 ... task1)
    pthread_create(...thread2 ... task2)
    ...
    signal all workers to start
    wait for all workers fo finish
    do any clean up
}
taks1()
{
    wait for start
    perform task, synchronize as needed if accessing shared resources
    done
}
taks2()
{
    wait for start
    perform task, synchronize as needed if accessing shared resources
    done
}

2.3 线程池模型

避免频繁创建撤销,或者不确定数量的创建;线程的切换介于忙 / 闲之间,而非生死之间;

解偶作用:线程的创建与执行完全分开,方便维护;

放入一个池子中,可以给其他任务机型复用;
Linux 多线程 5:如何设计多线程程序

boss/worker 模型,常与线程池模型结合实现;
code:
multiThread.cpp

2.4 流水线模型:

流水线的每一个阶段应尽可能相等时间;

pthread_t Thread[N]
Queues[N]

//initial thread
{
    place all input into stage1 is queue
    pthread_create(&(Thread[1]...stage1...))
    pthread_create(&(Thread[2]...stage1...))
    pthread_create(&(Thread[3]...stage1...))
    //...
}
void *stageX(void *X)
{
    loop 
    suspend until input unit is in queue
    loop while XQueue is not empty
        dequeue intput unit 
        process input unit
        enqueue input unit into next stage s queu
    end loop
    until done

    return NULL;
}
pipeline, 类似 ARM 流水线;

比如放电影过程:
读文件,解码,渲染,显示

./vlc

cd /proc/pid/
ls task // 看到有多个线程

查看每个线程的系统调用
sudo cat /proc/pid/task/ppid/stack 

3 Amdahl 定律

α: 串行化比例;
β 多核间通信消耗;
Linux 多线程 5:如何设计多线程程序

当 β = 0 时,就是 Amdahl 定律

4 多线程与 I /O

4.1 异步 IO

Aio_read 函数会立马返回,自动创建一个线程去干活;类似 boos-worker 模型的实现;

#include <aio.h>
int aio_read(struct aiocb *aiocbp);
int aio_write(struct aiocb *aiocbp);

Linux 多线程 5:如何设计多线程程序

4.2 多路复用

(1)select

Linux 多线程 5:如何设计多线程程序

因为存在一个唤醒文件查询过程,每调用一次,还需要重新设置观察文件集,当文件数量过大时,效率会地下;
while(1)死循环内添加 fd 到 fdset, 等待 select, 检查谁 read-FD_ISSET()

while(1){FD_ZERO(&rset);
    for (int i=0; i <5; i++){FD_SET(fds[i],&rset);
    }

    puts("round again");
    select(max+1, &rset, NULL, NULL, NULL);

    for (int i=0; i < 5; i++) {if (FD_ISSET(fds[i],&rset)) {memset(buffer,0,MAXBUF);
            read(fds[i], buffer, MAXBUF);
            puts(buffer);
        }
    }
}

(2)epoll

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout); 

epoll 在循环体外添加 FD, 循环体内 epoll_wait, 返回值知道 ready 的文件 fd

for (int i=0; i <5; i++){
    static struct epoll_event ev;
    memset(&client,0,sizeof(client));
    addrlen = sizeof(client)
    ev.data.fd = accept(sockfd, (struct sockaddr*)&client, &addrlen);
    en.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev);
}

while(1){puts("round again");
    nfds = epoll_wait(epfd, events, 5, 10000);

    for (int i=0; i < nfds; i++) {memset(buffer,0,MAXBUF);
        read(events[i].data.fd,buffer,MAXBUF);
        puts(buffer);
    }
}

4.3 多线程文件冲突问题

文件是进程级的,一个线程访问文件,会导致另外一个线程访问文件的偏移位置等不可控;
Linux 多线程 5:如何设计多线程程序

C 库提供的 pread,pwrite 函数解决多线程 offset 等冲突问题;(实现方法,共享数据加锁恢复)

正文完
 
admin
版权声明:本站原创文章,由 admin 2022-01-25发表,共计3547字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
评论(没有评论)