Linux线程

文章目录

一、线程与进程

1.1.线程的介绍

典型的 UNIX/Linux 进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。进程是程序执行时的一个实例,是担当分配系统资源(CPU 时间、内存等)的基本单位。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。线程是操作系统能够进行运算调度的最小单位。 总结:进程------资源分配的最小单位,线程------程序执行的最小单位。

1.2.线程与进程的关系

在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器,线程相当于寄生在进程里,一个进程至少有一个线程,进程有独立的地址空间。当一个进程崩溃后,在保护模式下不会对其它进程产生影响,而在进程下的线程会随着该进程崩溃。

下面表格是线程与进程的对比:

操作 进程开销 线程开销
创建时间 ~1ms ~0.01ms
上下文切换 较重 很轻
内存占用 独立地址空间 共享地址空间
通信开销 需要内核介入 直接内存访问

线程与进程的内存空间关系,在同一个进程的线程中,所有线程共享进程的代码段、数据段、堆、BSS 段(Block Started by Symbol专门用于存放未初始化的全局变量和静态变量),每个线程都有自己的栈区,用于存放变量和函数调用等。

二、关于多线程的 API 函数

多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,面对操作线程的 API 有创建、退出和等待操作;互斥锁是面对保护资源的操作有创建、销毁、加锁和解锁;条件变量是面对线程间交流的操作有创建、销毁、触发和广播,共 12 个函数:

2.1.线程

面对线程有创建、退出和销毁三个函数,下面是创建线程函数原型:

c 复制代码
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, 				//定义一个pthread_t 类型的线程ID,并把它的地址传进来
				   const pthread_attr_t *restrict attr, 	//定义该线程属性,比如它的栈大小、优先级,一般写NULL设置默认
				   void *(*start_rtn)(void *), 				//编写一个这个新线程做事情的函数,并把函数名传进来
				   void *restrict arg);						//传参数给新线程的函数里
//返回值:若成功返回0,否则返回错误编号

退出进程有多种方法,

  • 线程只是从启动例程中返回,返回值是线程的退出码
  • 线程可以被同一进程中的其他线程取消
  • 线程调用pthread_exit:

rval_ptr 是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用 pthread_join 函数访问到这个指针。下面是该函数的原型:

c 复制代码
#include <pthread.h>
int pthread_exit(void *rval_ptr);	//这是线程结束时要传递出去的返回值,它可以指向任何类型的数据
									//什么都不传就填NULL	

线程等待,如果不调用该函数等待线程完成,主函数就早早结束进程,会导致僵尸线程的诞生,严重浪费资源,因此,当主函数完成了自己的工作,需要调用该函数等待线程的工作完成才能结束整个进程,下面是该函数的原型:

c 复制代码
#include <pthread.h>
int pthread_join(pthread_t thread,	//线程ID 
				 void **rval_ptr);	//需要另外定义一个变量来承接线程函数所返回的数据,void **是固定格式主要是能承接各种类型的数据

获取线程 ID 的函数:

c 复制代码
#include <pthread.h>
pthread_t pthread_self(void);	//返回值是线程ID

下面是各个 API 函数的综合应用,将主线程和新线程的值打印出来,该程序的输出结果可能先运行新线程再是主线程,它们的顺序是不定的,会相互抢夺资源:

c 复制代码
#include <stdio.h>
#include <pthread.h>

void *t1WorkPlace(void *arg)
{
    static int ret;

    ret = *(int *)arg + 10;
    printf("T1:This is new thread:%ld\n", (unsigned long)pthread_self());
    printf("T1:arg = %d\n", *(int *)arg);

    pthread_exit((void *)&ret);
}

int main()
{
    pthread_t threadID;
    int arg = 250;
    int ret;
    int *getRet = NULL;

    ret = pthread_create(&threadID, NULL, t1WorkPlace, (void *)&arg);
    if(ret == 0)
    {
            printf("Create thread success\n");
    }


    pthread_join(threadID, (void **)&getRet);
    printf("Get value from new thread is:%d\n", *getRet);
    printf("Main thread:%ld\n", pthread_self());

    return 0;
}

2.2.互斥量

互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。为了防止一个全局变量被一个线程修改的过程中,被另一个线程打断,进而导致数据的错误,所以需要一把锁保护数据,这样有序的整改同一个变量,数据的准确性就提高了。下面是创建和销毁互斥量的函数:

c 复制代码
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, 		 //需要定义一个pthread_mutex_t类型的变量为锁的ID
					   const pthread_mutexattr_t *restrict attr);//定义锁的属性,例如:普通锁、递归锁、检错锁等,填NULL用默认属性
int pthread_mutex_destroy(pthread_mutex_t mutex);	//互斥锁的ID
//返回值:若成功返回0,否则返回错误编号

下面是加锁与解锁的函数原型:

c 复制代码
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t mutex);		//锁的ID
int pthread_mutex_unlock(pthread_mutex_t mutex);
//返回值:若成功返回0,否则返回错误编号

下面例子:创建两个新的线程,每个线程都有自己的工作,新的两个线程加上锁,必须等待某一个线程运行完成才轮到另一个线程,两个新线程运行的过程中可能被主线程打断,因为主线程并没有互斥锁保护:

c 复制代码
#include <stdio.h>
#include <pthread.h>

pthread_mutex_t mutex;

void *t1Fun(void *arg)
{
    int i;
    pthread_mutex_lock(&mutex);
    for(i = 0; i < 5; i++)
    {
            printf("T1:%ld---------------------------------------------\n", pthread_self());
            printf("T1:Get data From main:%d\n", *(int *)arg);
            sleep(1);
    }
    pthread_mutex_unlock(&mutex);
}

void *t2Fun(void *arg)
{
    pthread_mutex_lock(&mutex);
    printf("T2:%ld\n", pthread_self());
    printf("T2:Get data From main:%d\n", *(int *)arg);
    pthread_mutex_unlock(&mutex);
}

int main()
{
    pthread_t t1, t2;
    int ret;
    int arg = 250;


    pthread_mutex_init(&mutex, NULL);

    ret = pthread_create(&t1, NULL, t1Fun, (void *)&arg);
    ret = pthread_create(&t2, NULL, t2Fun, (void *)&arg);
    if(ret == 0)
    {
            printf("Main:Create success\n");
    }

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&mutex);

    return 0;
}

2.3.条件变量

条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。下面是创建及销毁条件变量的函数:

c 复制代码
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,				//定义一个pthread_cond_t类型的条件变量
 				      const pthread_condattr_t *restrict attr);		//除非需要创建一个非默认属性的条件变量,否则pthread_cont_init函数的attr参数可以设置为NULL
int pthread_cond_destroy(pthread_cond_t cond);		//填入定义的条件变量
// 返回:若成功返回0,否则返回错误编号

下面函数是等待条件的到来,只有某个线程给了合适的条件,就会进行该线程的操作:

c 复制代码
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, 	//定义一个pthread_cond_t类型的变量
					  pthread_mutex_t *restrict mutex);		//需要使用哪一把锁
// 返回:若成功返回0,否则返回错误编号

下面是触发条件的函数:

c 复制代码
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t cond);
// 返回:若成功返回0,否则返回错误编号

创建新的两个线程,这两个线程分别对全局变量 g_data 进行修改,每当 g_data 加到 5 的时候,就触发 t1 线程的条件变量,并对 g_data 重新赋值为 0,当 t1 线程的变量 i 为 2 的时候,退出整个进程:

c 复制代码
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

pthread_mutex_t mutex;
pthread_cond_t cond;
int g_data = 0;

void *t1Fun(void *arg)
{
    int i = 0;
    printf("t1:%ld\n", pthread_self());
    while(1)
    {
            pthread_cond_wait(&cond, &mutex);
            printf("t1:-----------------R-----U-----N-----------------------------------\n");
            printf("t1:data=%d\n", g_data);
            g_data = 0;
            sleep(1);
            i++;
            printf("i = %d\n", i);
            if(i == 2)
            {
                    exit(1);
            }
    }
}

void *t2Fun(void *arg)
{
    printf("T2:Get data From main:%d\n", *(int *)arg);
    while(1)
    {
            pthread_mutex_lock(&mutex);
            printf("t2:data:%d\n", g_data);
            g_data++;
            if(g_data == 5)
            {
                    pthread_cond_signal(&cond);
            }
            sleep(1);
            pthread_mutex_unlock(&mutex);
    }
}

int main()
{
    pthread_t t1, t2;
    int ret;
    int arg = 250;


    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond , NULL);

    ret = pthread_create(&t1, NULL, t1Fun, (void *)&arg);
    ret = pthread_create(&t2, NULL, t2Fun, (void *)&arg);
    if(ret == 0)
    {
            printf("Main:Create success\n");
    }

    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}
相关推荐
A小辣椒4 小时前
TShark:基础知识
linux
AlfredZhao6 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao20 小时前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪1 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux