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

776次阅读
没有评论

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等冲突问题;(实现方法,共享数据加锁恢复)

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