Linux系统下多线程管理

程序、进程和线程

学习多线程编程的实现方法之前,首先要搞清楚什么是线程,这就要从程序、进程和线程三者的关系和区别讲起。

大家常常编写程序,程序其实就是一系列指令(代码)的集合,我们通常将它编写在一个或者多个文件中。例如,C 语言程序通常编写在后缀名为 .c 的文件中,Python 程序编写在后缀名为 .py 的文件中,我们通常将存有程序的文件称为“源文件”。

程序以源文件的方式存储在外存(比如硬盘、U盘等)中,只有运行的时候才会被载入内存。对于支持并行的操作系统来说,必须为每一个运行的程序分配所需的资源(内存空间、输入输出设备等),并确保同时运行的程序之间不会相互干扰,为此,操作系统将每一个运行着的程序视为一个进程:

  • 操作系统以进程为单位,为每个进程分配执行所需要的资源;
  • 原则上,各个进程之间不允许访问对方的资源;
  • 操作系统实时监控着每个进程的执行状态,必要时可以强制其终止执行。

也就是说在操作系统看来,每个载入内存执行的程序都是一个进程。操作系统以进程为单位分配资源,各个进程相互独立,执行过程互不干扰。

同一时间,操作系统可以运行多个应用程序(进程),每个应用程序(进程)还可以同时执行多个任务,例如迅雷支持同时下载多个文件,QQ 也支持同时和多个好友聊天。同一进程中,执行的每个任务都被视为一个线程。

线程和进程之间的关系,与工厂和工人之间的关系非常相似。一个进程好比是一座工厂,一个线程就如同这个工厂中的一个工人。工厂可以容纳多个工人,每个工人负责完成一项具体的任务。工厂负责为所有工人提供必要的资源(电力、产品原料、食堂、厕所等),所有工人共享这些资源。

也就是说,一个进程中可以包含多个线程,所有线程共享进程拥有的资源。当然,每个线程也可以拥有自己的私有资源。下图给您展示进程和线程之间的关系:
进程和线程的关系
如图 1 所示,所有线程共享的进程资源有:
代码:即应用程序的代码;
数据:包括全局变量、函数内的静态变量、堆空间的数据等;
进程空间:操作系统分配给进程的内存空间;
打开的文件:各个线程打开的文件资源,也可以为所有线程所共享,例如线程 A 打开的文件允许线程 B 进行读写操作。

各个线程也可以拥有自己的私有资源,包括寄存器中存储的数据、线程执行所需的局部变量(函数参数)等。

多线程编程的实现方法

了解了程序、进程和线程之间的关系后,多线程的含义就很容易理解了,它指的是一个进程中拥有多个(≥2)线程。通常,我们将编写多线程程序的过程称为“多线程编程”。
Linux 上编写多线程程序,可以借助 <pthread.h> 头文件提供的一些函数,常用的函数有如下几个:

  1. pthread_create()

pthread_create() 函数专门用来创建线程,语法格式如下:

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

各个参数的含义是:
thread:接收一个 pthread_t 类型变量的地址,每个 pthread_t 类型的变量都可以表示一个线程。
attr:手动指定新线程的属性,我们可以将其置为 NULL,表示新建线程遵循默认属性。
start_routine:以函数指针的方式指明新建线程需要执行哪个函数。
arg:向 start_routinue() 函数的形参传递数据。将 arg 置为 NULL,表示不传递任何数据。

如果成功创建线程,pthread_create() 函数返回数字 0,否则返回一个非零值。各个非零值都对应着不同的宏,指明创建失败的原因,常见的宏有以下几种:
EAGAIN:系统资源不足,无法提供创建线程所需的资源。
EINVAL:传递给 pthread_create() 函数的 attr 参数无效。
EPERM:传递给 pthread_create() 函数的 attr 参数中,某些属性的设置为非法操作,程序没有相关的设置权限。

以上这些宏都定义在 <errno.h> 头文件中,如果想使用这些宏,需提前引入此头文件。

  1. pthread_exit()

pthread_exit() 函数用于终止线程执行,语法格式如下:

void pthread_exit(void *retval);

retval 参数指向的数据将作为线程执行结束时的返回值,如果不需要返回任何数据,将其置为 NULL 即可。注意,retval 不能指向函数内部的局部变量,否则会导致程序运行出错甚至崩溃。

  1. pthread_cancel()
    在多线程程序中,一个线程可以借助 pthread_cancel() 函数向另一个线程发送“终止执行”的信号。

pthread_cancel() 函数的语法格式如下:

int pthread_cancel(pthread_t thread);

thread 参数用于指定接收信号的目标线程。当成功发送“终止执行”的信号时,函数返回值为 0,否则返回非零数。

  1. pthread_join()

pthread_join() 函数的功能主要有两个,分别是:
接收目标线程执行结束时的返回值;
释放目标线程占用的进程资源。

pthead_join() 函数的语法格式如下:

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

thread 参数用于指定目标线程;retval 参数用于存储接收到的返回值。实际场景中,调用 pthread_join() 函数可能仅是为了及时释放目标线程占用的资源,并不想接收它的返回值,这种情况下可以将 retval 置为 NULL。
pthread_join() 函数会一直阻塞当前线程,直至目标线程执行结束,阻塞状态才会消除。如果成功等到了目标线程执行结束(成功获取到目标线程的返回值),pthread_join() 函数返回数字 0,否则返回非零数。

  1. pthread_self()

获取调用线程的标识ID

  1. pthread_cleanup_push()______pthread_cleanup_pop()

线程清除。线程终止有两种情况:正常终止和非正常终止。线程主动调用pthread_exit()或者从线程函数中return都将使线程正常退出,这是可预见的退出方式;非正常终止是线程在其它线程的干预下,或者由于自身运行出错(比如访问非法地址)而退出,这种退出方式是不可预见的。不论是可预见的线程终止还是异常终止,都回存在资源释放的问题,如何保证线程终止时能顺利地释放掉自己所占用的资源,是一个必须考虑的问题。
从pthread_cleanup_push()的调用点到pthread_cleanup_pop()之间的程序段中的终止动作(包括调用pthread_exit()和异常终止,不包括return)都将执行pthread_cleanup_push()所指定的清理函数。
在这里插入图片描述

编译:使用命令:gcc thread_create.c -o thread_create -lpthread编译,注意不要忘了加 -lpthread
因为pthread库不是Linux默认链接库,链接时必须指定使用libpthread.a库(ubuntu11.10这些库在/usr/lib/i386-linux-gnu路径下),在编译选项中需添加-lpthread参数.

symbol lookup error  问题解决  https://blog.youkuaiyun.com/dfman1978/article/details/6104544

转载自: https://blog.youkuaiyun.com/mybelief321/article/details/9377379

Linux进程间通信——使用信号量

https://blog.youkuaiyun.com/ljianhui/article/details/10243617

Linux多线程——使用信号量同步线程

https://blog.youkuaiyun.com/ljianhui/article/details/10813469/

什么是信号量

线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。

而只有0和1两种取值的信号量叫做二进制信号量,在这里将重点介绍。而信号量一般常用于保护一段代码,使其每次只被一个执行线程运行。我们可以使用二进制信号量来完成这个工作。

信号量的接口和使用

信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件 semaphore.h中。

  1. sem_init函数
    该函数用于创建信号量,其原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);

该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。调用成功时返回0,失败返回-1.

  1. sem_wait函数
    该函数用于以原子操作的方式将信号量的值减1。原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。它的原型如下:
int sem_wait(sem_t *sem);

sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.

  1. sem_post函数
    该函数用于以原子操作的方式将信号量的值加1。它的原型如下:
int sem_post(sem_t *sem);

与sem_wait一样,sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.

  1. sem_destroy函数
    该函数用于对用完的信号量的清理。它的原型如下:
int sem_destroy(sem_t *sem);

成功时返回0,失败时返回-1.

  1. sem_getvalue
int sem_getvalue(sem_t *sem, int *sval);

获取信号量 sem 的当前值,把该值保存在 sval,若有 1 个或者多个线程正在调用 sem_wait 阻塞在该信号量上,该函数返回阻塞在该信号量上进程或线程个数。

一个信号量同步线程的例子:https://blog.youkuaiyun.com/qq_19923217/article/details/82902442

使用信号量同步线程

下面以一个简单的多线程程序来说明如何使用信号量进行线程同步。在主线程中,我们创建子线程,并把数组msg作为参数传递给子线程,然后主线程等待直到有文本输入,然后调用sem_post来增加信号量的值,这样就会立刻使子线程从sem_wait的等待中返回并开始执行。线程函数在把字符串的小写字母变成大写并统计输入的字符数量之后,它再次调用sem_wait并再次被阻塞,直到主线程再次调用sem_post增加信号量的值。

#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
 
//线程函数
void *thread_func(void *msg);
sem_t sem;//信号量
 
#define MSG_SIZE 512
 
int main()
{
	int res = -1;
	pthread_t thread;
	void *thread_result = NULL;
	char msg[MSG_SIZE];
	//初始化信号量,其初值为0
	res = sem_init(&sem, 0, 0);
	if(res == -1)
	{
		perror("semaphore intitialization failed\n");
		exit(EXIT_FAILURE);
	}
	//创建线程,并把msg作为线程函数的参数
	res = pthread_create(&thread, NULL, thread_func, msg);
	if(res != 0)
	{
		perror("pthread_create failed\n");
		exit(EXIT_FAILURE);
	}
	//输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”
	printf("Input some text. Enter 'end'to finish...\n");
	while(strcmp("end\n", msg) != 0)
	{
		fgets(msg, MSG_SIZE, stdin);
		//把信号量加1
		sem_post(&sem);
	}
 
	printf("Waiting for thread to finish...\n");
	//等待子线程结束
	res = pthread_join(thread, &thread_result);
	if(res != 0)
	{
		perror("pthread_join failed\n");
		exit(EXIT_FAILURE);
	}
	printf("Thread joined\n");
	//清理信号量
	sem_destroy(&sem);
	exit(EXIT_SUCCESS);
}
 
void* thread_func(void *msg)
{
	//把信号量减1
	sem_wait(&sem);
	char *ptr = msg;
	while(strcmp("end\n", msg) != 0)
	{
		int i = 0;
		//把小写字母变成大写
		for(; ptr[i] != '\0'; ++i)
		{
			if(ptr[i] >= 'a' && ptr[i] <= 'z')
			{
				ptr[i] -= 'a' - 'A';
			}
		}
		printf("You input %d characters\n", i-1);
		printf("To Uppercase: %s\n", ptr);
		//把信号量减1
		sem_wait(&sem);
	}
	//退出线程
	pthread_exit(NULL);
}

Linux多线程——使用互斥量同步线程

互斥量是另一种用于多线程中的同步访问方法,它允许程序锁住某个对象,使得每次只能有一个线程访问它。为了控制对关键代码的访问,必须在进入这段代码之前锁住一个互斥量,然后在完成操作之后解锁。

它们的定义与使用信号量的函数非常相似,它们的定义如下:

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
 
int pthread_mutex_lock(pthread_mutex_t *mutex);
 
int pthread_mutex_unlock(pthread_mutex_t *mutex);
 
int pthread_mutex_destroy(pthread_mutex_t *mutex);

它们的意义就如它们的名字所示的那样,成功时返回0,失败时返回错误代码,它们并不设置errno。

pthread_mutex_init函数中的参数mutexattr指定互斥量的属性,在这里我们并不关心互斥量的属性,所以把它设置为NULL,使用默认属性即可。同样的,pthread_mutex_lock和pthread_mutex_unlock都是原子操作,如果一个线程调用 pthread_mutex_lock试图锁住互斥量,而该互斥量,又被其他线程锁住(占用),则该线程的pthread_mutex_lock调用就会阻塞,直到其他线程对该互斥量进行解锁,该线程才能获得该互斥量,pthread_mutex_lock调用才会返回。

注意,使用互斥量的默认属性,如果程序试图对一个已经加锁的互斥量调用pthread_mutex_lock,程序就会阻塞,而又因为拥有互斥量的这个线程正是现在被阻塞的线程,所以这个互斥量就永远不会被解锁,也就是说,程序就会进入死锁的状态。在使用时要多加注意,确保在同一个线程中,对加锁的互斥再次进行加锁前要对其进行解锁。
————————————————
原文链接:https://blog.youkuaiyun.com/ljianhui/article/details/10875883

Linux暂停、继续进程

使用ps aux获取希望暂停的进程号
kill -STOP pid将该进程暂停
如果要让它恢复到后台,用kill -CONT 1234 (很多在前台运行的程序这样是不行的)
如果要恢复到前台,请在当时运行该进程的那个终端用jobs命令查询暂停的进程。
然后用 fg 〔job号〕把进程恢复到前台。

参考内容:
https://www.cnblogs.com/mfryf/archive/2012/09/24/2700042.html
https://blog.youkuaiyun.com/tim_phper/article/details/53536621

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值