首页 > 系统相关 >Linux 基础入门操作 第十章 多线程实现

Linux 基础入门操作 第十章 多线程实现

时间:2024-09-25 16:51:37浏览次数:10  
标签:__ mutex int Linux 第十章 线程 pthread cond 多线程

10 线程介绍

线程是进程的一条执行路径。每个线程共享其所附属的进程的所有的资源,包括打开的文件、页表(因此也就共享整个用户态地址空间)、信号标识及动态分配的内存等等。

线程和进程的关系是:线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一物理内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。

线程技术早在 60 年代就被提出,但真正应用多线程到操作系统中去,是在 80 年代中期。传统的 Unix也支持线程的概念,但是在一个进程(process)中只允许有一个线程,这样多线程就意味着多进程。现在,多线程技术已经被许多操作系统所支持,包括 Windows/NT,当然,也包括 Linux。

10.1 选择线程好处

使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。我们知道,在 Linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的 30 倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。

使用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为 static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。

  1. 提高应用程序响应。这对图形界面的程序尤其有意义,当一个操作耗时很长时,整个系统都会等待这个操作,此时程序不会响应键盘、鼠标、菜单的操作,而使用多线程技术,将耗时长的操作(time consuming)置于一个新的线程,可以避免这种尴尬的情况。
  2. 使多 CPU 系统更加有效。操作系统会保证当线程数不大于 CPU 数目时,不同的线程运行于不同的CPU 上。
  3. 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。

Linux 系统下的多线程遵循 POSIX 线程接口,称为 pthread。编写 Linux 下的多线程程序,需要使用头文件 pthread.h,连接时需要使用库 libpthread.a。LIBC 中的 pthread 库提供了大量的 API 函数,为用户编写应用程序提供支持。

10.2 常用函数库

线程创建函数:

int pthread_create (pthread_t * thread_id, __const pthread_attr_t * __attr, void*(*__start_routine) (void *),void *__restrict __arg)

线程创建函数第一个参数为指向线程标识符的指针,第二个参数用来设置线程属性,第三个参数是线程运行函数的起始地址,最后一个参数是运行函数的参数。这里,我们的函数 thread 不需要参数,所以最后一个参数设为空指针。第二个参数我们也设为空指针,这样将生成默认属性的线程。当创建线程成功时,函数返回 0,若不为 0 则说明创建线程失败,常见的错误返回代码为 EAGAIN 和 EINVAL。前者表示系统限制创建新的线程,例如线程数目过多了;后者表示第二个参数代表的线程属性值非法。创建线程成功后,新创建的线程则运行参数三和参数四确定的函数,原来的线程则继续运行下一行代码。

获得父进程 ID:

pthread_t pthread_self (void)

测试两个线程号是否相同:

int pthread_equal (pthread_t __thread1, pthread_t __thread2)

线程退出:

void pthread_exit (void *__retval)

一个线程的结束有两种途径,一种是象我们上面的例子一样,函数结束了,调用它的线程也就结束了;另一种方式是通过函数 pthread_exit 来实现。
唯一的参数是函数的返回代码,只要 pthread_join 中的第二个参数 thread_return 不是 NULL,这个值将被传递给 thread_return。最后要说明的是,一个线程不能被多个线程等待,否则第一个接收到信号的线程成功返回,其余调用 pthread_join 的线程则返回错误代码 ESRCH

等待指定的线程结束:

int pthread_join (pthread_t __th, void **__thread_return)

第一个参数为被等待的线程标识符,第二个参数为一个用户定义的指针,它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数,调用它的函数将一直等待到被等待的线程结束为止,当函数返回时,被等待线程的资源被收回。

互斥量初始化:

pthread_mutex_init (pthread_mutex_t *,__const pthread_mutexattr_t *)

销毁互斥量:

int pthread_mutex_destroy (pthread_mutex_t *__mutex)

再试一次获得对互斥量的锁定(非阻塞):

int pthread_mutex_trylock (pthread_mutex_t *__mutex)

锁定互斥量(阻塞)

int pthread_mutex_lock (pthread_mutex_t *__mutex)

解锁互斥量:

int pthread_mutex_unlock (pthread_mutex_t *__mutex)

使用互斥锁来可实现线程间数据的共享和通信,互斥锁一个明显的缺点是它只有两种状态:锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足,它常和互斥锁一起使用。使用时,条件变量被用来阻塞一个线程,当条件不满足时,线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量,它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来,条件变量被用来进行线线程间的同步。

条件变量初始化:

int pthread_cond_init (pthread_cond_t *__restrict __cond, __const pthread_condattr_t *__restrict __cond_attr)

其中 cond 是一个指向结构 pthread_cond_t 的指针,cond_attr 是一个指向结构 pthread_condattr_t 的指针。结构 pthread_condattr_t 是条件变量的属性结构,和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用,默认值是 PTHREAD_ ROCESS_PRIVATE,即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为 pthread_cond_destroy(pthread_cond_t cond)。

销毁条件变量 COND

int pthread_cond_destroy (pthread_cond_t *__cond)

唤醒线程等待条件变量:

int pthread_cond_signal (pthread_cond_t *__cond)

它用来释放被阻塞在条件变量 cond 上的一个线程。多个线程阻塞在此条件变量上时,哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是,必须用保护条件变量的互斥锁来保护这个函数,否则条件满足信号又可能在测试条件和调用 pthread_cond_wait 函数之间被发出,从而造成无限制的等待。

等待条件变量(阻塞)

int pthread_cond_wait (pthread_cond_t *__restrict __cond, pthread_mutex_t *__restrict __mutex)

线程解开 mutex 指向的锁并被条件变量 cond 阻塞。线程可以被函数 pthread_cond_signal 和函数pthread_cond_broadcast 唤醒,但是要注意的是,条件变量只是起阻塞和唤醒线程的作用,具体的判断条件还需用户给出,例如一个变量是否为 0 等等,这一点我们从后面的例子中可以看到。线程被唤醒后,它将重新检查判断条件是否满足,如果还不满足,一般说来线程应该仍阻塞在这里,被等待被下一次唤醒。这
个过程一般用 while 语句实现。

在指定的时间到达前等待条件变量:

int pthread_cond_timedwait (pthread_cond_t *__restrict __cond, pthread_mutex_t *__restrict__mutex, __const struct timespec *__restrict __abstime)

它比函数 pthread_cond_wait()多了一个时间参数,经历 abstime 段时间后,即使条件变量不满足,阻塞也被解除。

10.3 案例代码

生产者线程不断顺序地将 0 到 1000 的数字写入共享的循环缓冲区,同时消费者线程不断地从共享的循环缓冲区读取数据。

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "pthread.h"
#define BUFFER_SIZE 16
/* 设置一个整数的圆形缓冲区 */
struct prodcons {
	int buffer[BUFFER_SIZE]; /* the actual data */
	pthread_mutex_t lock; /* mutex ensuring exclusive access to buffer */
	int readpos, writepos; /* positions for reading and writing */
	pthread_cond_t notempty; /* signaled when buffer is not empty */
	pthread_cond_t notfull; /* signaled when buffer is not full */
};

/*初始化缓冲区*/
void init(struct prodcons * b)
{
	pthread_mutex_init(&b->lock, NULL);
	pthread_cond_init(&b->notempty, NULL);
	pthread_cond_init(&b->notfull, NULL);
	b->readpos = 0;
	b->writepos = 0;
}
/* 向缓冲区中写入一个整数*/
void put(struct prodcons * b, int data)
{
	pthread_mutex_lock(&b->lock);
	/*等待缓冲区非满*/
	while ( (b->writepos + 1) % BUFFER_SIZE == b->readpos) 
	{
	    printf("wait for not full\n");
	    pthread_cond_wait(&b->notfull, &b->lock);
    }
	/*写数据并且指针前移*/
	b->buffer[b->writepos] = data;
	b->writepos++;
	if (b->writepos >= BUFFER_SIZE)
	     b->writepos = 0;
	/*设置缓冲区非空信号*/
	pthread_cond_signal(&b->notempty);
	pthread_mutex_unlock(&b->lock);
}
/*从缓冲区中读出一个整数 */

int get(struct prodcons * b)
{
	int data;
	pthread_mutex_lock(&b->lock);
	/* 等待缓冲区非空*/
	while (b->writepos == b->readpos) {
		printf("wait for not empty\n");
		pthread_cond_wait(&b->notempty, &b->lock);
	}
	/* 读数据并且指针前移 */
	data = b->buffer[b->readpos];
	b->readpos++;
	if (b->readpos >= BUFFER_SIZE) b->readpos = 0;
	/* 设置缓冲区非满信号*/
	pthread_cond_signal(&b->notfull);
	pthread_mutex_unlock(&b->lock);
	return data;
}

生产程序

void * producer(void * data)
{
	int n;
	for (n = 0; n < 1000; n++) {
		printf(" put-->%d\n", n);
		put(&buffer, n);
	}
		put(&buffer, OVER);
		printf("producer stopped!\n");
	return NULL;
}

消费程序

void * consumer(void * data)
{
	int d;
	while (1) {
		d = get(&buffer);
		if (d == OVER ) break;
		printf(" %d-->get\n", d);
	}
	printf("consumer stopped!\n");
	return NULL;
}

主程序

int main(void)
{
		pthread_t th_a, th_b;
		void * retval;
		init(&buffer);
		pthread_create(&th_a, NULL, producer, 0);
		pthread_create(&th_b, NULL, consumer, 0);
	/* 等待生产者和消费者结束 */
		pthread_join(th_a, &retval);
		pthread_join(th_b, &retval);
	return 0;
}

标签:__,mutex,int,Linux,第十章,线程,pthread,cond,多线程
From: https://blog.csdn.net/huanghongqi11/article/details/142370056

相关文章

  • Linux基础——修改Bclinux8的内核启动顺序
    一、Grubby的参数(base)[root@NewOSBC8~]#grubby--helpUsage:grubby[OPTION...]--add-kernel=kernel-pathaddanentryforthespecifiedkernel--args=argsdefaultargumentsforthenewkernelornewarguments......
  • linux semaphore信号量操作
    信号量(semaphore)是操作系统中最常见的同步原语之一。spinlock是实现忙等待锁,而信号量则允许进程进入睡眠状态。下面将分析信号量的获取是释放操作。1、数据结构数据结构定义和初始化如下:include/linux/semaphore.h/*Pleasedon'taccessanymembersofthisstruc......
  • Linux常用命令(Mysql)
    --删除表内数据(Mysql)usedc;#切换到待删除表所在的数据库truncatetable[表名]#删除表--数据库导入SQL文件数据(Mysql)sourcea.sql;--SQL增删改查insertintostudent(id,name,sex,birth)values('01','赵雷','男','1990');deletefromstudentwhereid=......
  • Linux安装MQTT 服务器(图文教程)
    MQTT(MessageQueuingTelemetryTransport)是一种轻量级的消息传输协议,专为低带宽和不稳定的网络环境设计,非常适合物联网(IoT)应用。官网地址:https://www.emqx.com/一、版本选择根据自己的操作系统进行下载即可,推荐使用rpm安装方式。下载地址:https://www.emqx.com/zh/downloads-and-i......
  • Linux中MySQL配置主主复制操作
    一、GTIDGTID(GlobalTransactionIdentifier)是MySQL的一种用于标识分布式环境中事务的全局唯一标识符。它在MySQL的主从复制场景中尤为重要,尤其是在使用MariaDB或MySQL5.6及更高版本的环境中。GTID由两部分组成:服务器ID(标识执行该事务的服务器)和事务序号(表示在该服务器上执......
  • 女生学Linux云计算怎么样?
    现如今,生活压力较大,就业找工作也比较难,而为了能够获得满意的工作、稳定的发展,很多小伙伴都想要找一个薪酬高的行业,于是不少人将目光瞄准IT行业。而作为当下热门的技术,Linux云计算成为香饽饽,那么0基础女生转行学Linux云计算难吗?以下是详细的内容介绍。首先,我可以肯定的告诉......
  • linux 切换阿里云镜像源
    目录linux切换阿里云镜像源备份原有文件:创建阿里云CentOS仓库文件:清理缓存并更新软件包列表:测试是否成功:linux切换阿里云镜像源centos7安装好后,发现外网可以ping通,但是yum一直报错,看报错内容为镜像源问题于是切换镜像源备份原有文件:在进行任何更改之前,请确保备份原有的仓......
  • 【越学学糊涂的Linux系统】Linux指令篇(2)
    一、echo指令:✔️✔️在终端中显示文本内容或向文件中写入文本Ⅰ.基本用法:0x00打印字符串:打印字符串/显示文本内容;可以用双引号作为文本内容⬇️⬇️更推荐用单引号这里我将字符串打印出来了。和printf的功能一样;......
  • linux集群 keepalived+nginx实现高可用集群
    用keepalived配置高可用搭建高可用集群高可用集群,即“HA集群”,也常称作“双机热备”,用于关键业务。常见实现高可用的开源软件有heartbeat和keepalived,其中keepalived还有负载均衡的功能。这两个软件类似,核心原理都是通过心跳线连接两台服务器,正常情况下由一台服务器提供服务,......
  • centos(linux):用命令设置用户的shell以及/bin/false和/sbin/nologin的区别
    一,/bin/false和/sbin/nologin作为shell时的区别1,/bin/false/bin/false是一个什么都不做,立即返回非零退出状态的命令。它通常用于禁止用户登录用户不会收到任何错误或提示信息,登录尝试简单地被拒绝,没有任何解释2,/sbin/nologin/sbin/nologin是一个专门设计来阻止用户登录的程......