本文主要介绍如何通过 pthread 库进行多线程编程,并通过以下例子进行说明。

  • 基于莱布尼兹级数计算 \(\pi\) .
  • 多线程归并排序

参考文章:

API 介绍

pthread_create

作用:新建一个线程。

函数原型:

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

参数解析:

  • pthread_t *thread 用于缓存新线程的 pid .
  • const pthread_attr_t *attr 制定新线程的 attr ,如果为 NULL,那么将使用默认的 attr
  • start_routine 是新线程即将进入的执行函数。
  • arg 向新线程传递的某些参数,一般封装为结构体传入。

线程的中止可以通过以下方式:

  • 调用 pthread_exit(void *retval) , 其中 retval 可以通过 pthread_join 获得。
  • start_routine 函数直接 return .
  • 该线程被取消 (See pthread_cancel) .
  • 线程所属的进程调用了 exit , 或者该进程的 main 函数中执行了 return .

pthread_join

等待某个线程结束。

函数原型:

int pthread_join(pthread_t thread, void **retval);

参数解析:

  • thread 是某个线程的 pid .

  • retval 用于获取线程 start_routine 的返回值 .

基本用法请看下面的「双线程计算 \(\pi\)」,该例子同时能够回答为什么 retvalvoid** 类型而不是 void * 类型。

pthread_attr_t

pthread_attr_t 的定义如下:

struct __pthread_attr
{
struct sched_param __schedparam;
void *__stackaddr;
size_t __stacksize;
size_t __guardsize;
enum __pthread_detachstate __detachstate;
enum __pthread_inheritsched __inheritsched;
enum __pthread_contentionscope __contentionscope;
int __schedpolicy;
};

与之相关的 API,请看:

man pthread_attr

Examples

创建线程

下面是一个简单的多线程例子,用于演示 pthread_createpthread_join 的基本用法。

该例子创建 4 个线程,通过 order[i] 分别标号,线程的工作内容是输出本线程的标号。

所涵盖的知识点:

  • 如何创建线程
  • 如何向线程传递参数:通过对 void *arg 进行强制类型转换实现。
  • pthread_join 的作用:如果去掉 pthread_join 调用,那么程序很可能是没有输出的。因为在进入各个线程的 worker 函数时,main 函数已经结束,这时候所有线程都被强制终止。
#include <stdio.h>
#include <pthread.h>
const int N = 4;
void* worker(void *arg)
{
int *pid = (int *)arg;
printf("%d ", *pid);
return NULL;
}
int main()
{
pthread_t pid[N] = {0};
const int order[] = {0, 1, 2, 3};
int i = 0;
for (; i < N; i++)
pthread_create(&pid[i], NULL, worker, (void *)&order[i]);
for (i = 0; i < N; i++)
pthread_join(pid[i], NULL);
return 0;
}

双线程计算 π

要求:

  • 基于莱布尼兹级数:1 - 1/3 + 1/5 - 1/7 + 1/9 - ... = PI/4
  • 使用主线程 + 辅助线程的方式

涵盖知识点:

  • 如何向不同的线程传递不同参数
  • 如何获取线程的结果

代码实现:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
const int N = 1e8;
typedef struct { int start, end; } param_t;
typedef struct { double value; } result_t;
void *worker(void *arg)
{
param_t *param = (param_t *)arg;
result_t *res = (result_t *)malloc(sizeof(result_t));
int i = param->start;
for (; i <= param->end; i++)
{
if (i % 2) res->value += 1.0 / (2 * i - 1);
else res->value -= 1.0 / (2 * i - 1);
}
return res;
} double master(void *arg)
{
double res = 0.0;
param_t *param = (param_t *)arg;
int i = param->start;
for (; i <= param->end; i++)
{
if (i % 2) res += 1.0 / (2 * i - 1);
else res -= 1.0 / (2 * i - 1);
}
return res;
} int main()
{
pthread_t tid = 0;
param_t p1 = {1, N / 2}, p2 = {N / 2 + 1, N};
pthread_create(&tid, NULL, worker, &p2);
double val = master(&p1);
result_t *res = NULL;
pthread_join(tid, (void **)&res);
printf("PI = %f\n", 4 * (val + res->value));
free(res);
}

多线程计算 π

要求:

  • 适应 N 核心的 CPU
  • 不能使用全局变量,必须通过传递参数与 join 获取返回值实现

代码实现:

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
const int N = 1e3;
const int NR_CPU = 8;
typedef struct { int start, end; } param_t;
typedef struct { double value; } result_t;
void *worker(void *arg)
{
param_t *p = (param_t *)arg;
int i = p->start;
result_t *res = malloc(sizeof(result_t));
for (; i < p->end; i++)
{
if (i % 2) res->value += 1.0 / (2 * i - 1);
else res->value -= 1.0 / (2 * i - 1);
}
return res;
} int main()
{
param_t params[NR_CPU];
pthread_t pids[NR_CPU] = {0};
const int step = N / NR_CPU;
int i = 0;
for (; i < NR_CPU; i++)
{
params[i].start = i * step + 1;
params[i].end = params[i].start + step;
}
params[NR_CPU - 1].end = N;
for (i = 0; i < NR_CPU; i++) pthread_create(&pids[i], NULL, worker, &params[i]);
result_t *res = NULL;
double pi = 0.0;
for (i = 0; i < NR_CPU; i++)
{
pthread_join(pids[i], (void **)&res);
pi += res->value;
if (res) free(res), res = NULL;
}
pi *= 4;
printf("PI = %f\n", pi);
return 0;
}

多线程归并排序

要求:

  • 把数组分为若干个区间,每个区间单独通过一个线程排序
  • 最后所有区间
#include <assert.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
const int N = 1e6;
const int NR_CPU = 4;
typedef struct
{
int *nums;
int start, end;
} param_t;
int check(const int *nums, int len)
{
int i = 1;
for (; i < len; i++)
if (nums[i] < nums[i - 1])
return 0;
return 1;
}
int cmp(const void *a, const void *b) { return (*(int *)a) - (*(int *)b); }
void *worker(void *arg)
{
param_t *p = (param_t *)arg;
int *start = p->nums + p->start;
int n = p->end - p->start;
qsort(start, n, sizeof(int), cmp);
return NULL;
}
// merge [start, mid) and [mid, end)
void merge(const int *nums, int start, int mid, int end)
{
int *p = malloc(sizeof(int) * (end - start));
const int *p1 = nums + start, *p2 = nums + mid;
int len1 = mid - start, len2 = end - mid;
int idx = 0, i = 0, j = 0;
while (i < len1 && j < len2)
{
if (p1[i] < p2[j]) p[idx++] = p1[i++];
else p[idx++] = p2[j++];
}
while (i < len1) p[idx++] = p1[i++];
while (j < len2) p[idx++] = p2[j++];
memcpy((void *)(nums + start), (void *)p, sizeof(int) * idx);
}
int main()
{
srand(time(NULL));
int nums[N] = {0};
int i = 0;
for (; i < N; i++) nums[i] = random() % N; param_t params[NR_CPU];
int step = N / NR_CPU;
for (i = 0; i < NR_CPU; i++)
{
params[i].nums = nums;
params[i].start = i * step;
params[i].end = params[i].start + step;
}
params[NR_CPU - 1].end = N;
pthread_t pids[NR_CPU] = {0};
for (i = 0; i < NR_CPU; i++) pthread_create(&pids[i], NULL, worker, &params[i]);
for (i = 0; i < NR_CPU; i++) pthread_join(pids[i], NULL);
while (step < N)
{
int start = 0;
while (start < N)
{
int mid = start + step;
int end = mid + step;
if (mid > N) mid = N;
if (end > N) end = N;
merge(nums, start, mid, end);
start = end;
}
step *= 2;
}
assert(check(nums, N));
}

pthread 多线程基础的更多相关文章

  1. clone的fork与pthread_create创建线程有何不同&pthread多线程编程的学习小结(转)

    进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合,这些资源在Linux中被抽 象成各种数据对象:进程控制块.虚存空间.文件系统,文件I/O.信号处理函数.所以创建一个进程的 过程就是这 ...

  2. Java基础知识笔记(四:多线程基础及生命周期)

    一.多线程基础 编写线程程序主要是构造线程类.构造线程类的方式主要有两种,一种是通过构造类java.lang.Thread的子类,另一种是通过构造方法实现接口java.lang.Runnable的类. ...

  3. Java多线程干货系列—(一)Java多线程基础

    前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程:进程中负责程序执行的 ...

  4. JAVASE02-Unit010: 多线程基础 、 TCP通信

    多线程基础 . TCP通信 * 当一个方法被synchronized修饰后,那么 * 该方法称为同步方法,即:多个线程不能同时 * 进入到方法内部执行. package day10; /** * 当多 ...

  5. JAVASE02-Unit09: 多线程基础

    Unit09: 多线程基础 * 线程 * 线程用于并发执行多个任务.感官上像是"同时"执行 *  * 创建线程有两种方式. * 方式一: * 继承线程并重写run方法来定义线程要执 ...

  6. java多线程基础

    多线程基础 读书练习照猫画虎 package Threadtest; import java.util.Date; import java.util.concurrent.ArrayBlockingQ ...

  7. C#编程总结(二)多线程基础

    C#编程总结(二)多线程基础 无论您是为具有单个处理器的计算机还是为具有多个处理器的计算机进行开发,您都希望应用程序为用户提供最好的响应性能,即使应用程序当前正在完成其他工作.要使应用程序能够快速响应 ...

  8. swift开发多线程篇 - 多线程基础

    swift开发多线程篇 - 多线程基础 iOS 的三种多线程技术 (1)NSThread  使用NSThread对象建立一个线程非常方便 但是!要使用NSThread管理多个线程非常困难,不推荐使用 ...

  9. Java多线程干货系列(1):Java多线程基础

    原文出处: 嘟嘟MD 前言 多线程并发编程是Java编程中重要的一块内容,也是面试重点覆盖区域,所以学好多线程并发编程对我们来说极其重要,下面跟我一起开启本次的学习之旅吧. 正文 线程与进程 1 线程 ...

随机推荐

  1. 推荐几款好用的python编辑器

    1.自带的IDLE:  (1)交互式代码编辑.在>>>提示符后输入python代码,按Enter键就可以显示代码命令执行结果. (2)脚本式代码编辑.选择File菜单里的newFil ...

  2. 自动创建新序列号的Cookies脚本

    已知一个网站在被访问的时候会读取电脑上存储的cookies 如果已经有cookie变量存在 则在存在的变量后按顺序增加新的序列 如电脑上有vst1变量的cookie了 那么新用户则自动创建为 vst2 ...

  3. 多测师讲解接口测试 _postman(下)_高级讲师肖sir

    关联接口 定义:上个接口返回的参数作为下一个接口的入参 1)接口1:查询出所有的州,自治区,直辖市,省(且发送请求不需要入参) 接口url地址: http://www.webxml.com.cn/We ...

  4. 多测师讲解_007 hashlib练习

    #Hash,译做"散列",也有直接音译为"哈希"的.把任意长度的输入,通过某种hash算法,变换成固定长度的输出,该输出就是散列值,也称摘要值.该算法就是哈希函 ...

  5. k8s集群,使用pvc方式实现数据持久化存储

    环境: 系统 华为openEulerOS(CentOS7) k8s版本 1.17.3 master 192.168.1.244 node1 192.168.1.245 介绍: 在Kubernetes中 ...

  6. MeteoInfoLab脚本示例:获取气团轨迹每个节点的气象数据

    读取HYSPLIT输出的轨迹数据文件和相应时间的气象数据文件,生成轨迹图层,循环每条轨迹的节点,读出该节点的经度.纬度.气压.时间,通过对气象数据插值获得该节点的气象数据.脚本程序: #------- ...

  7. MeteoInfoLab脚本示例:AVHRR HDF数据

    这里演示读取和绘制AVHRR hdf格式数据,以sst(海表面温度)为例. 脚本程序: #Add data file f = addfile('D:/Temp/hdf/2006001-2006005. ...

  8. spring boot:实现图片文件上传并生成缩略图(spring boot 2.3.1)

    一,为什么要给图片生成缩略图? 1, 用户上传的原始图片如果太大,不能直接展示在网站页面上, 因为不但流费server的流量,而且用户打开时非常费时间, 所以要生成缩略图. 2,服务端管理图片要注意的 ...

  9. python去除特殊字符

    去除数字,特殊字符,只保留汉字 ? 1 2 3 4 5 6 7 8 import re    s = '1123*#$ 中abc国' str = re.sub('[a-zA-Z0-9'!"# ...

  10. Windows(WSL2) Linux子系统搭建Docker环境

    摘要:本文主要介绍了如何再Windows(WSL2)中启用Linux系统中,并搭建Docker环境. WSL是适用于 Linux 的 Windows 子系统可让开发人员按原样运行 GNU/Linux ...