linux程序设计——多线程(第十二章)

12.8    多线程

之前,总是让程序的主线程仅仅创建一个线程,这节将演示如何在同一个程序中创建多个线程,然后如何以不同于其启动顺序将它们合并在一起。此外,还演示多线程编程时容易出现的时序问题.

编写程序thread8.c

/*************************************************************************
 > File Name:    thread8.c
 > Description:  thread8.c程序创建多个线程,然后以不同于启动顺序将它们合并在一起
 > Author:       Liubingbing
 > Created Time: 2015年07月07日 星期二 19时37分45秒
 > Other:        thread8.c程序存在一个小漏洞,如果主线程运行足够快时,可能修改传递引用的参数thread_index,造成问题.见thread8a.c
 ************************************************************************/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>

#define NUM_THREADS 6

void *thread_function(void *arg);

int main(){
	int res;
	pthread_t a_thread[NUM_THREADS];
	void *thread_result;
	int thread_index;

	for (thread_index = 0; thread_index < NUM_THREADS; thread_index++) {
		/* pthread_create创建新线程,这里创建了一个线程ID的数组 */
		res = pthread_create(&(a_thread[thread_index]), NULL, thread_function, (void *)&thread_index);
		if (res != 0) {
			perror("Thread creation failed");
			exit(EXIT_FAILURE);
		}
		sleep(1);
	}
	printf("Waiting for threads to finish...\n");
	/* 主线程中等待合并这些子线程,但并不是以创建它们的顺序来合并 */
	for (thread_index = NUM_THREADS - 1; thread_index >= 0; thread_index--) {
		res = pthread_join(a_thread[thread_index], &thread_result);
		if (res == 0) {
			printf("Picked up a thread\n");
		} else {
			perror("pthread_join failed");
		}
	}
	printf("All done\n");
	exit(EXIT_SUCCESS);
}

void *thread_function(void *arg) {
	int my_number = *(int *)arg;
	int rand_num;

	printf("thread_function is running. Argument was %d\n", my_number);
	/* 创建的线程等待一段随机的时间退出运行 */
	rand_num = 1 + (int)(9.0 * rand() / (RAND_MAX + 1.0));
	sleep(rand_num);
	printf("Bye from %d\n", my_number);
	pthread_exit(NULL);
}

运行thread8.c,看到如下结果:

这个程序首先创建一个线程ID的数组,如下所示:

pthread_t a_thread[NUM_THREADS];

然后通过循环创建多个线程,如下所示:

for (thread_index = 0; thread_index < NUM_THREADS; thread_index++) {
    res = pthread_create(&(a_thread[thread_index]), NULL, thread_function, (void *)&thread_index);
}

创建出的线程等待一段随机的时间后退出运行,如下所示:

void *thread_function(void *arg) {
    int my_number = *(int *) arg;
    int rand_num;
    printf("thread_function is running. Argument was %d\n", my_number);
    rand_num = 1 + (int)(9.0 * rand() / RAND_MAX + 1.0));
    sleep(rand_num);
    printf("Bye from %d\n", my_number);
    pthread_exit(NULL);
}

在主线程中,等待合并这些子线程,但并不是以创建它们的顺序来合并,如下所示:

for (thread_index = NUM_THREADS -1; thread_index >= 0; thread_index--) {
    res = pthread_join(a_thread[thread_index], &thread_result);
    ...
}

这个程序有一个小漏洞,如果将sleep调用从启动线程的循环中删除,它将会变得很明显。很可能会看到一些奇怪的现象,比如一些线程以相同的参数被启动,类似下图:

为什么会出现这样的问题?启动线程时,线程函数的参数是一个局部变量,这个变量在循环中被更新,引起问题的代码行是:

for (thread_index = 0; thread_index < NUM_THREADS; thread_index++) {
    res = pthread_create(&(a_thread[thread_index]), NULL, thread_function, (void *)&thread_index);
}

如果主线程运行的足够快(因为删除sleep(1)之后,主线程相对新线程就非常快了),就可能改变某些线程的参数(即thread_index)。此时,传递引用不是恰当的选择,而传值是正确的.当对共享变量和多个执行路径没有做到足够重视时,程序就可能出现这样的错误行为。编写线程程序时需要在设计上特别小心。要改正这个问题,可以直接传递给这个参数的值,如下所示:

res = pthread_create(&(a_thread[thread_index]), NULL, thread_function, (void *)thread_index);

还有修改thread_function函数,如下所示:

int my_number = (int) arg;

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-15 18:24:19

linux程序设计——多线程(第十二章)的相关文章

Linux与云计算——第二阶段Linux服务器架设 第一十二章:数据库搭建—PostgreSQL

Linux与云计算--第二阶段Linux服务器架设 第一十二章:数据库搭建-PostgreSQL 1.1 安装PostgreSQL [1] 安装并启动PostgreSQL. [[email protected] ~]# yum -y install postgresql-server [[email protected] ~]# postgresql-setup initdb Initializing database ... OK [[email protected] ~]# vim /var

鸟哥的Linux私房菜——第十二章:档案的压缩与打包

视频链接: 土豆: B站(推荐): 本章目录: 1. 压缩档案的用途与技术: (为啥要压缩嘞?因为比如存储一个数字1,前7位补充0浪费,压缩技术就是把空的那些用上,省硬盘空间,下载的时候也省带宽)2. Linux 系统常见的压缩指令:2.1 compress (这个是最老的压缩指令,现在不怎么用了)2.2 gzip, zcat (新一代的压缩指令,代替了compress,zcat是读取gzip和compress的压缩数据的指令)2.3 bzip2, bzcat   (bzip2更高效,压缩比高,

JavaScript高级程序设计:第十二章

DOM1级主要定义的是HTML和XML文档的底层结构.DOM2和DOM3级则在这个结构的基础上引入了更多的交互能力,也支持了更高级的XML特性.为此DOM2和DOM3级分为许多模块,这些模块如下: DOM2级核心: DOM2级视图: DOM2级事件: DOM2级样式: DOM2级遍历和范围: DOM2级HTML. 一.DOM变化 DOM2级和3级的目的在于扩展DOM API,以满足操作XML的所有需求,同时提供更好的错误处理及特性检测能力. 1.针对XML命名空间的变化 有了XML命名空间,不同

第二十二章 Linux文件比较,文本文件的交集、差集与求差:comm命令

第二十二章 Linux文件比较,文本文件的交集.差集与求差:comm命令 名词解释 comm 命令 可以用于两个文件之间的比较,它有一些选项可以用来调整输出,以便执行交集.求差.差集操作. 交集:打印两个文件所共有的行 求差:打印出指定文件所包含的其不相同的行. 差集:打印出包含在一个文件中,但不包含在其他指定文件中的行. 语法 comm(选项)(参数) 选项 -1 :不显示在第一个文件出现的内容: -2 :不显示在第二个文件中出现的内容: -3 :不显示同时在两个文件中都出现的内容. ? 参数

“全栈2019”Java多线程第四十二章:获取线程与读写锁的保持数

难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多线程第四十二章:获取线程与读写锁的保持数 下一章 "全栈2019"Java多线程第四十三章:查询是否有线程在等待读写锁 学习小组 加入同步学习小组,共同交流与进步. 方式一:关注头条号Gorhaf,私信"Java学习小组". 方式二:关注公众号Gorhaf,回复&quo

第二十二章 TCP/IP层的实现

                      第二十二章    TCP/IP层的实现        我比较喜欢先难后易,如果把GPU显示管理.和网络管理拿下后:我会从头整理.改写一遍APO操作系统.这样,就会形成APO操作系统的锥形.也获得了全局观.内核CPU线路.和用户CPU线路,你可以将它们看成是独立的2个32位CPU核:内核CPU主要任务是实时处理.硬件中断,256个实时线程包含了一些中断程序的后半部.用户CPU主要是动态优先级进程.线程调度,各种应用程序的运行:2个核之间是通过消息交互.句

第十二章 读书笔记

第十二章  Linux 驱动程序中的 阻塞和非阻塞 I/O 等待队列是 Linux 内核的一种实现进程休眠的技术.在上一章介绍的自旋锁使用的是不断循环 的方式阻塞 Linux 驱动,这种方式很占 CPU 资源.而等待队列的你眠技术可以大大降低休眠进程对 CPU 资源的消耗,信号量就是利用等待队列实现了对临界区的锁定.本节将介绍等待队列的原 理以及相关的函数和宏,最后会使用一个完整的例子来演示如何使用等待队列休眠和唤醒进程. 运行在用户空间的应用程序可以使用 select函数检测设备文件是否可以读

进击的Python【第十二章】:mysql介绍与简单操作,sqlachemy介绍与简单应用

进击的Python[第十二章]:mysql介绍与简单操作,sqlachemy介绍与简单应用 一.数据库介绍 什么是数据库? 数据库(Database)是按照数据结构来组织.存储和管理数据的仓库,每个数据库都有一个或多个不同的API用于创建,访问,管理,搜索和复制所保存的数据.我们也可以将数据存储在文件中,但是在文件中读写数据速度相对较慢.所以,现在我们使用关系型数据库管理系统(RDBMS)来存储和管理的大数据量.所谓的关系型数据库,是建立在关系模型基础上的数据库,借助于集合代数等数学概念和方法来

[CSAPP笔记][第十二章并发编程]

第十二章 并发编程 如果逻辑控制流在时间上是重叠,那么它们就是并发的(concurrent).这种常见的现象称为并发(concurrency). 硬件异常处理程序,进程和Unix信号处理程序都是大家熟悉的例子. 我们主要将并发看做是一种操作系统内核用来运行多个应用程序的机制. 但是,并发不仅仅局限于内核.它也可以在应用程序中扮演重要的角色. 例如 Unix信号处理程序如何允许应用响应异步事件 例如:用户键入ctrl-c 程序访问虚拟存储器的一个未定义的区域 其他情况 访问慢速I/O设备 当一个应

第十二章 并发编程 学习笔记

第十二章 并发编程 进程是程序级并发,线程是函数级并发. 三种基本的构造并发程序的方法: 进程:每个逻辑控制流是个一个进程,由内核进行调度和维护. I/O多路复用:应用程序在一个进程的上下文中显式地调度他们自己的逻辑流. 线程:运行在单一进程上下文中的逻辑流,由内核进行调度. 12.1 基于进程的并发编程 构造并发程序最简单的方法就是用进程. 使用大家都很熟悉的函数例如: fork exec waitpid 关于在父.子进程间共享状态信息:共享文件表,但不共享用户地址空间. 进程又独立的地址空间