多线程与线程同步

linux基础

多线程特点

线程是轻量级的进程,在Linux环境下线程的本质仍是进程。在计算机上运行的程序是一组指令及指令参数的组合,指令按照既定的逻辑控制计算机运行。操作系统会以进程为单位,分配系统资源。

可以理解为:进程是资源分配的最小单位,线程是操作系统调度的最小单位。

在概念上了解线程与进程的区别:

  • 进程有自己独立的地址空间,多个线程共用一个地址空间

    • 线程更加节省系统资源,效率不仅可以保持的,而且能够更高

    • 在一个地址空间中多个线程独享;每个线程都有属于自己的栈区、寄存器

    • 在一个地址空间中多个线程共享;代码段、堆区、全局变量区、文件描述符

  • 线程是程序最小的执行单位,进程是操作系统中最小的资源分配单位

    • 每个进程对应一个虚拟地址空间,一个进程只能抢一个CPU时间片

    • 一个地址空间中可以划分出多个线程,在有效资源基础上,能够抢更多的CPU时间片

  • 线程的CPU调度比进程快

    • 上下文切换:保存上一个任务的状态,下次切换回该任务时,加载这个状态继续运行,任务从保存到再次加载就是一次上下文切换
  • 线程更加廉价,启动速度更快,退出也快,对系统的冲击小

创建线程

线程函数

每个线程都有一个唯一的线程ID,ID类型为pthread_t(无符号长整型),使用pthread_self函数获取当前线程ID。

pthread_t pthread_self(void);  // 获取当前线程的线程ID

在一个进程中调用线程创建函数,就可以得到一个子线程,和进程不同,需要给每一个创建出来的线程指定一个处理函数,否则这个线程无法工作。

#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);
// Compile and link with -pthread, 线程库的名字叫pthread, 全名: libpthread.so libptread.a
  • 参数

    • thread:传出参数,线程ID

    • attr:线程属性

    • start_routine:回调函数,函数指针

    • arg:传参

  • 返回值

    • 创建成功返回0

线程的退出

在编写多线程程序的时候,如果想要让线程退出,但是不能导致虚拟空间的释放,可说使用线程退出函数。只要调用该函数,线程马上退出,并且不会影响其他进程正常运行,不管是在子进程还是主进程都可以使用。

#include <pthread.h>
void pthread_exit(void *retval);
  • 参数:retval,线程退出时候要回传的参数

线程回收

线程函数

线程和进程是一样的,子进程退出的时候其内核资源主要有主线程回收,线程库中提供的线程回收函数叫做pthread_join,函数为阻塞函数,如果还有子线程运行,那么调用该函数会阻塞,子线程退出函数解除阻塞进行资源的回收,函数调用一次,只能回收一个子进程。

#include "pthread.h"
int pthread_join(pthread_t thread, void **retval);
  • 参数:
    • thread,回收的函数ID

    • arr,传出参数的地址,参数由pthread_exit传出

线程分离

在某种情况下,程序中的主线程有属于自己的业余处理流程,如果让主线程负责子进程的资源回收,调用pthread_t只要子进程不退出主进程就会一直被阻塞,主线程的任务就不能被执行了。

线程库提供了线程分离函数pthread_detach,调用这个函数之后指定的子线程就可以和主线程分离,当子线程退出的时候,其占用的内核资源会被系统的其他进程接管并回收。使用线程分离之后,就无法在该进程释放子进程了。

#include "pthread.h"
int pthread_detach(pthread_t thread);
  • 参数:
    • read,分离的函数ID

线程取消

线程取消的意思是在特定情s况下在一个线程中杀死另一个线程,在线程A中调用线程取消函数,指定杀死线程B,此时线程B无法被杀死,在线程B中调用一次系统调用,从用户区切换到内核区,否则线程B可以一直运行。

#include <pthread.h>
int pthread_cancel(pthread_t thread);
  • 参数:thread:要杀死的线程ID

线程比较函数

#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
  • 返回值:相同为非0,不相等为0

示例代码

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

/* 线程回调函数传参的结构体 */
typedef struct{
	char name[10];
	int num;
}thread_arg_t;

void *thread_callback(void *arg)
{
	thread_arg_t *demo = (thread_arg_t *)arg;

	// 输出自己的ID 
	printf("我是子线程,PID = %ld\n", pthread_self());
	for(int i = 0; i < 9; i++) {
		if(i == 5) {
			// 定义退出是的参数,这是使用static
			// 同一虚拟地址中的线程,不能共享栈区数据,但是可以共享全局数据区和堆区数据
			static thread_arg_t retval;
			strcpy(retval.name, demo->name);
			retval.num = demo->num;

			// 子线程退出
			pthread_exit(&retval);
		}
		printf("i = %d\n", i);
	}
	return NULL;
}

int main()
{
	pthread_t tid;
	thread_arg_t arg_t;
	strcpy(arg_t.name, "liustu");
	arg_t.num = 20;

	// 创建一个线程 参数 线程id,属性, 回调函数, 传参
	pthread_create(&tid, NULL, thread_callback, &arg_t);
	if(tid == 0) {
		printf("创建线程成功,PID:%ld\n", tid);
		printf("我是主线程,PID:%ld\n", pthread_self());
	}

	for(int i = 0; i < 3; i++) {
		printf("i = %d\n", i);
	}

	// 主线程等待,让自线程运行
	// sleep(2);
	
	// 主线程调用退出函数,地址空间不会被释放
	// pthread_exit(NULL);

	// 创建一个指针
	void *ptr = NULL;
	//  ptr 为传出参数,join函数内部让这个指针指向一个有效的内寸
	pthread_join(tid, &ptr);
	// 打印消息
	thread_arg_t *demo = (thread_arg_t *)ptr;
	printf("接收到子线程的数据:name %s num %d\n", demo->name, demo->num);

	return 0;
}

相关文章

迈进Makefile的世界(入门)

简介 Linux的`make`程序用来自动话编译大型源码,实现只需要一个`make`执行就可以全自动完成。 `make`能自动化完成,是因为项目路径下提供了一个`Makefile`文件,由该文件负责告诉`make`,应该去编译和链接该项目程序。 `make`起初只针对C语言开发,但它实际应用并不限定C语言,而是执行Linux命令去应用到任意项目,甚至不是编程语言。 >此外`make`...

linux基础

线程池

线程池的原理 我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:如果并发得线程数量很多,并且每个线程都是执行一个时间很短得任务结束了,这样频繁得创建线程会降低系统的效率,因为频繁创建线程和销毁线程需要时间。 线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都可以默认得堆栈大小,以默认优先级运...

linux基础

线程同步

线程同步的概念 多个线程对内存中的共享资源访问时,让线程进行线性的方式,有顺序的访问。线程对内存的这种访问方式就是线程同步。 下面是一个两个线程同时对变量num,进行加1的操作的demo,但是最终结果与预想结果,有很大差异。下面我们将分析并解决线程同步的问题。 ``` #include <pthread.h> #include <stdio.h> #include <unistd.h> #...

linux基础