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

704次阅读
没有评论

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