当前位置: 首页 > news >正文

Linux系统编程——线程概述、线程控制和线程私有数据

一、线程概述

  在许多经典的操作系统教科书中,总是把进程定义为程序的执行实例,它并不执行什么, 只是维护应用程序所需的各种资源,而线程则是真正的执行实体。在一个进程中的多个执行路线叫做线程。为了让进程完成一定的工作,进程必须至少包含一个线程。
  线程又叫轻量级进程(LWP)。


线程会共享进程的一些资源,也有一些资源是线程独立拥有的。但是不同进程的线程是不共享资源的。

【共享】:
  代码区、数据区、堆区(注意没有栈区)、环境变量和命令行参数、文件描述符、信号处理函数、当前目录、用户 ID 和组 ID 等。

【非共享】:
   ID、寄存器值、栈内存、调度策略和优先级、信号掩码、errno变量以及线程私有数据等。

  也可以说线程是包含在进程中的一种实体。它有自己的运行线索,可完成特定任务。可与其他线程共享进程中的共享变量及部分环境。可通过相互之间协同来完成进程所要完成的任务。

二、线程控制

1、线程标识

  就像每个进程都有一个进程号一样,每个线程也有一个线程号。进程号在整个系统中是唯一的,但线程号不同,线程号只在它所属的进程环境中有效。进程号用 pid_t 数据类型表示,是一个非负整数。线程号则用 pthread_t 数据类型来表示,Linux 使用无符号长整数表示。有的系统在实现 pthread_t 的时候,用一个结构体来表示,所以在可移植的操作系统实现不能把它做为整数处理。

(1)获取线程号

#include <pthread.h>
pthread_t pthread_self(void);

功能: 获取线程号。
参数: 无。
返回值: 调用线程的线程 ID 。

(2)线程号的比较

#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);

功能: 判断线程号 t1 和 t2 是否相等。为了方便移植,尽量使用函数来比较线程 ID。
参数: t1,t2:待判断的线程号。
返回值: 相等:非 0;不相等:0。

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>int main()
{pthread_t thread_id;thread_id = pthread_self(); // 获取线程号printf("thread id = %lu\n", thread_id);if (pthread_equal(thread_id, pthread_self())) // 线程号比较printf("Equal\n");elseprintf("not Equal\n");return 0;
}
// 线程函数的程序在 pthread 库中,故链接时要加上参数 -lpthread。

2、线程创建

#include <pthread.h>
int pthread_create( pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg );

功能: 创建一个线程。
参数:
        thread:线程标识符地址;
        attr:线程属性结构体地址,通常设置为 NULL;
        start_routine:线程函数的入口地址;
        arg:传给线程函数的参数。
返回值: 成功:0;失败:非0。

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>void *fun(void *arg)
{int *p = (int *)arg;printf("child thread pid = %d\n", getpid());printf("child thread id = %lu\n", pthread_self());printf("child thread *arg = %d\n", *p);sleep(1);
}int main()
{pthread_t tid;int n = 10;// 创建一个线程,fun是线程函数,n是传给线程函数的参数,// 强制转换为void *类型。int err = pthread_create(&tid, NULL, fun, (void *)&n);if (err != 0){// 如果创建失败,打印出错信息。fprintf(stderr, "can't create thread:%s\n", strerror(err)); exit(1);}printf("main pid = %d\n", getpid());printf("main thread id = %lu\n", pthread_self());printf("main child thread id = %lu\n", tid);// 线程创建时并不能保证哪个线程先运行:是新建的线程还是调用线程。sleep(2); return 0;
}

3、线程终止

(1)线程退出

#include <pthread.h>
void pthread_exit(void *retval);

功能: 主要用于终止正在运行的线程,但并不释放资源。
参数:
retval:来带出线程的退出状态信息。
返回值: 无。
  在线程过程函数或者被线程过程函数直接或间接调用的函数中,调用 pthread_exit 函数,其效果都与在线程过程函数中执行 return 语句效果一样 – 终止调用线程。注意,在任何线程中调用 exit 函数,被终止的都是进程。当然随着进程的终止,隶属于该进程的包括调用线程在内的所有线程也都一并终止。

【Note】:
  如果thread线程函数从return返回,则retval存放的是thread线程函数的返回值;如果从pthread_exit返回,则retval存放的是pthread_exit的参数。

(2)线程取消

#include <pthread.h>
int pthread_cancel(pthread_t thread);

功能: 对参数指定的线程发送取消的请求(必须是同一进程中的其他线程)。
参数:
thread:线程号。
返回值: 成功:0;失败:错误码。
  该函数只是向线程发出取消请求,并不等于线程终止。缺省情况下,线程在收到取消请求以后,并不会立即终止,而是仍继续运行,直到其达到某个取消点。在取消点处,线程检查其自身是否已被取消,若是则立即终止。当线程调用一些特定函数时,取消点会出现。

(3)线程回收(阻塞)

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

功能: 等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
参数:
        thread:被等待的线程号;
        retval:用来存储线程退出状态的指针的地址。
返回值: 成功:0;失败:非0。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>void *thrfun1(void *p)
{int sum = 0;for (int i = 1; i <= 10; ++i)sum += i;printf("子线程1退出\n");return (void *)sum; // 为了保持类型匹配,强制转换。
}void *thrfun2(void *p)
{printf("子线程2退出\n");pthread_exit((void *)2); // 本线程退出,不影响其他线程。
}void *thrfun3(void *p)
{while (1){printf("子线程3运行\n");sleep(1);}
}int main()
{pthread_t tid;void *it;pthread_create(&tid, NULL, thrfun1, NULL);pthread_join(tid, &it);printf("子线程1返回的数据是:%ld\n", (long)it);pthread_create(&tid, NULL, thrfun2, NULL);pthread_join(tid, &it);printf("子线程2返回的数据是:%ld\n", (long)it);pthread_create(&tid, NULL, thrfun3, NULL);sleep(3); // 主控线程运行三秒后取消子线程3pthread_cancel(tid); // 线程被取消// 回收由pthread_cancel终止的进程,返回值是一个宏(-1)pthread_join(tid, &it); printf("子线程3返回的数据是:%ld\n", (long)it);return 0;
}

(4)线程回收(非阻塞)

#include <pthread.h>
int pthread_detach(pthread_t thread);

功能: 主要用于将参数指定的线程标记为分离状态。
参数:
thread:线程号;
返回值: 成功:0;失败:错误码。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>void *thrfun(void *p)
{int n = 3;while (n--){printf("thread count %d\n", n);sleep(1);}return (void *)1;
}int main()
{pthread_t tid;void *it;int err;pthread_create(&tid, NULL, thrfun, NULL);// 如果没有pthread_detach,主控线程会阻塞回收子线程,// 并且打印返回状态和出错码。// 如果设置了pthread_detach,主控线程非阻塞回收子线程,// 这种情况下再调用pthread_join会出错。pthread_detach(tid);while (1){err = pthread_join(tid, &it);if (err != 0)fprintf(stderr, "thread %s\n", strerror(err));elsefprintf(stderr, "thread exit code %ld\n", (long)it);sleep(1);}return 0;
}

  对于分离状态的线程来说:当该线程终止后,会自动将资源释放给系统,不需要其他线程的加入/等待(即:立即回收资源),也就是说分离的线程无法被其他线程使用 pthread_join 进行等待。建议:对于新启动的线程来说,要么使用 pthread_detach 设置为分离状态,要么使用 pthread_join 设置为可加状态(即:pthread_join 和 pthread_detach 是互斥的)。

【Note】:

在任何一个时间点上,线程是可结合的(joinable),或者是分离的(detached)。一个可结合的线程能够被其他线程收回其资源和杀死;在被其他线程回收之前,它的存储器资源(如栈)是不释放的。相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时由系统自动释放。

三、线程私有数据

  有时应用程序设计中必要提供线程私有的全局变量,这个变量仅在线程中有效,但却可以跨过多个函数访问。比如在程序里可能需要每个线程维护一个链表,而会使用相同的函数来操作这个链表,最简单的方法就是使用同名而不同变量地址的线程相关数据结构。这样的数据结构可以由 Posix 线程库维护,成为线程私有数据 (Thread-specific Data,或称为TSD)。

1、创建线程私有数据

#include <pthread.h> 
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));

功能: 创建一个类型为 pthread_key_t 类型的私有数据变量( key )。
参数:
        key:在分配( malloc )线程私有数据之前,需要创建和线程私有数据相关联的键( key ),这个键的功能是获得对线程私有数据的访问权;
        destructor:清理函数名字( 如:fun )。当线程退出时,如果线程私有数据地址不是非 NULL,此函数会自动被调用。该函数指针可以设成 NULL ,这样系统将调用默认的清理函数。
返回值: 成功:0;失败:非0。
  不论哪个线程调用 pthread_key_create(),所创建的 key 都是所有线程可访问,但各个线程可根据自己的需要往 key 中填入不同的值,相当于提供了一个同名不同值的变量。

2、注销线程私有数据

#include <pthread.h> 
int pthread_key_delete(pthread_key_t key);

功能: 注销线程私有数据。这个函数并不会检查当前是否有线程正使用线程私有数据( key ),也不会调用清理函数 destructor() ,而只是将线程私有数据( key )释放以供下一次调用 pthread_key_create() 使用。
参数:
        key:待注销的私有数据。
返回值: 成功:0;失败:非0。

3、设置线程私有数据的关联

#include <pthread.h> 
int pthread_setspecific(pthread_key_t key, const void *value);

功能: 设置线程私有数据( key ) 和 value 关联,注意,是 value 的值(不是所指的内容)和 key 相关联。
参数:
        key:线程私有数据;
        value:和 key 相关联的指针。
返回值: 成功:0;失败:非0。

4、读取线程私有数据所关联的值

#include <pthread.h> 
void *pthread_getspecific(pthread_key_t key);

功能: 读取线程私有数据( key )所关联的值。
参数:
        key:线程私有数据;
返回值: 成功:线程私有数据( key )所关联的值;失败:NULL。

#include <stdio.h> 
#include <pthread.h> 
#include <unistd.h>pthread_key_t key;  // 私有数据,全局变量void echomsg(void *t) 
{ printf("[destructor] thread_id = %lu, param = %p\n", pthread_self(), t); 
} void *child1(void *arg) 
{ int i = 10;pthread_t tid = pthread_self(); //线程号printf("\nset key value %d in thread %lu\n", i, tid); pthread_setspecific(key, &i); // 设置私有数据printf("thread one sleep 2 until thread two finish\n\n");sleep(2); printf("\nthread %lu returns %d, add is %p\n", tid, *((int *)pthread_getspecific(key)), pthread_getspecific(key)); 
} void *child2(void *arg) 
{ int temp = 20;pthread_t tid = pthread_self();  //线程号printf("\nset key value %d in thread %lu\n", temp, tid); pthread_setspecific(key, &temp); //设置私有数据sleep(1); printf("thread %lu returns %d, add is %p\n", tid, *((int *)pthread_getspecific(key)), pthread_getspecific(key)); 
} int main(void) 
{ pthread_t tid1,tid2; pthread_key_create(&key, echomsg); // 创建pthread_create(&tid1, NULL, child1, NULL); pthread_create(&tid2, NULL, child2, NULL); pthread_join(tid1, NULL);pthread_join(tid2, NULL);pthread_key_delete(key); // 注销return 0; 
} 

http://www.mrgr.cn/news/66460.html

相关文章:

  • 介绍一下rand函数生成随机数(c基础)
  • 前沿吃瓜:如何看待linux社区将俄罗斯的linux贡献者“逐出”社区
  • cesium实现测面功能
  • vueui vxe-form 分享实现表单项的联动禁用,配置式表单方式的用法
  • 19. 架构重要需求
  • sicp每日一题[2.65]
  • 如何高效集成每刻与金蝶云星空的报销单数据
  • 代码随想录一刷——454.四数相加II
  • Jest进阶知识:测试快照 - 确保组件渲染输出正确
  • 2024年专业的10款数据恢复工具你都用过哪些?
  • 鸿蒙应用开发:下载功能
  • 【020】基于51单片机病房呼叫系统
  • 105. UE5 GAS RPG 搭建主菜单
  • Qt 环境实现视频和音频播放
  • 负载均衡与容错的基本原则
  • C#-类:索引器
  • 【xml转JSON】
  • windows_worm
  • 信号与噪声分析——第三节:随机过程的统计特征
  • 对于IIC的理解
  • 蓝桥杯2021年题解(IP补充)
  • QStackedWidget使用实例
  • Java 基于SpringBoot+Vue 的公交智能化系统,附源码、文档
  • 【C++篇】在秩序与混沌的交响乐中: STL之map容器的哲学探寻
  • 一些常规IP核功能
  • HOT100_最大子数组和