文章目录
一、线程是什么?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。线程包含了表示进程内执行环境必须的信息,其中包括进程中表示线程的线程ID、一组寄存器值、栈、调度优先级和策略、信号屏蔽字、errno常量以及线程私有数据。进程的所有信息对该进程的所有线程都是共享的,包括可执行的程序文本、程序的全局内存和堆内存、栈以及文件描述符。在Unix和类Unix操作系统中线程也被称为轻量级进程(lightweight processes),但轻量级进程更多指的是内核线程(kernel thread),而把用户线程(user thread)称为线程。
进程——资源分配的最小单位,线程——程序执行的最小单位
二、Linux 进程和线程的区别
相同点
都是实现多任务并发的技术手段, 二者都可以独立调度, 在多任务环境下, 功能上无差异。 二者都是系统独立管理的对象的个体, 所以在系统层面都可以实现对二者的控制, 并且在多任务程序中, 子进程(线程) 的调度一般和父进程平等竞争。
不同点
实现方式的差异
①线程和进程都可以被调度, 进程的个体是完全独立的, 线程间是彼此依存的, 多进程环境中, 任何一个进程的终止, 不会影响到其他进程。
②创建进程时采用 fork 创建子进程实体, 而创建线程时并不采用 clone 系统调用, 而是采用线程库函数。
③vfork()函数, 也是一个系统调用, 用来创建新进程。
多任务程序设计模式的区别
1 进程间是独立的, 所以在设计多进程程序时, 需要做到资源独立管理时就有
了天然优势, 而线程就显得麻烦多了。
②线程间的资源不独立, 看似是个缺点, 但是有的时候也成了优点。 多进程环境间完全独立, 要实现通信得采用进程间的通信方式, 通常是耗时间的。 而线程不用任何数据就是共享, 当多个子线程同时执行写入操作的时候需要实现互斥, 否则数据就”脏”了。
实体间(进程间, 线程间) 通信方式的不同
进程间通信方式有: (八种)
A. 共享内存 B.消息队列 C.信号量 D.有名管道 E.无名管道 F.信号
G.文件 H.socket
②线程间的通信方式都可以沿用进程间的通信方式(信号除外) , 并且还有独特的几种: (十三种)
A. 互斥量 B.自旋锁 C.条件变量 D.读写锁 E.线程信号 G.全局变量
控制方式的异同
①进程与线程的身份标示 ID 管理方式不一样, 进程的 ID 为 pid_t 类型, 实际为一个 int 型的变量。
②线程的 ID 是一个 Long 型变量, 它的范围大得多, 管理方式也不同。
资源管理方式的异同
①进程本身是资源分配的基本单位, 因而它的资源都是独立的, 如果有多进程间的共享资源, 就要用到进程间的通信方式了, 比如共享内存。 共享数据就放在共享内存去, 大家都可以访问, 为保证数据写入的安全, 加上信号量一同使用。 一般而言, 共享内存都是和信号量一起使用。
②线程间要使用共享资源不需要用共享内存, 直接使用全局变量即可, 或者 malloc()动态申请内存。
进程池和线程池的技术实现差别
三、线程的创建退出等待
线程创建
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号
当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID。attr参数用于定制各种不同的线程属性,暂可以把它设置为NULL,以创建默认属性的线程。
新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。
线程退出
int pthread_exit(void *rval_ptr);
单个线程可以通过以下三种方式退出,在不终止整个进程的情况下停止它的控制流:
1)线程只是从启动例程中返回,返回值是线程的退出码。
2)线程可以被同一进程中的其他线程取消。
3)线程调用pthread_exit:
rval_ptr是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到
线程等待
int pthread_join(pthread_t thread, void **rval_ptr);
// 返回:若成功返回0,否则返回错误编号
调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消。如果例程只是从它的启动例程返回i,rval_ptr将包含返回码。如果线程被取消,由rval_ptr指定的内存单元就置为PTHREAD_CANCELED。
可以通过调用pthread_join自动把线程置于分离状态,这样资源就可以恢复。如果线程已经处于分离状态,pthread_join调用就会失败,返回EINVAL。
如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。
代码示例
#include <stdio.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
void * attr(void *arg)
{
//static ret=10;
static char*str="hahahaha"; //这里必须用static延长局部变量的生命周期 否则函数结束 str就释放了
printf("t1:%ld pthread is create\n",(unsigned long)pthread_self());//打印线程ID
printf("t1:data is %d\n",*((int*)arg));//打印传过来的data值
pthread_exit((void *)str);//退出线程
}
int main()
{
int ret;
int data=100;
char *pret=NULL;
pthread_t t1;//线程id
ret=pthread_create(&t1,NULL,attr,(void *)&data);
//创建线程 第二个参数创建默认线程属性 创建的进程在attr函数运行 传递一个直接在后面写 传递多个写结构体 把地址写到第四个参数
if(ret==0){//返回0说明创建成功
printf("create pthread success\n");
}
printf("main:the pthread is %ld\n",(unsigned long)pthread_self()); //求调用线程ID
//int pthread_join(pthread_t thread, void **rval_ptr);
pthread_join(t1,(void **)&pret); //join可以收集exit返回值
printf("main:the ret=%s\n",pret);//将收集的exit返回值打印出来
return 0;
}
四、互斥锁
互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为可运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去等待它重新变为可用。在这种方式下,每次只有一个线程可以向前运行。
在设计时需要规定所有的线程必须遵守相同的数据访问规则。只有这样,互斥机制才能正常工作。操作系统并不会做数据访问的串行化。如果允许其中的某个线程在没有得到锁的情况下也可以访问共享资源,那么即使其它的线程在使用共享资源前都获取了锁,也还是会出现数据不一致的问题。
互斥变量用pthread_mutex_t数据类型表示。在使用互斥变量前必须对它进行初始化,可以把它置为常量PTHREAD_MUTEX_INITIALIZER(只对静态分配的互斥量),也可以通过调用pthread_mutex_init函数进行初始化。如果动态地分配互斥量(例如通过调用malloc函数),那么在释放内存前需要调用pthread_mutex_destroy。
1.创建及销毁互斥锁
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t mutex);
// 返回:若成功返回0,否则返回错误编号
2.加锁及解锁
int pthread_mutex_lock(pthread_mutex_t mutex);
int pthread_mutex_unlock(pthread_mutex_t mutex);
// 返回:若成功返回0,否则返回错误编号
代码示例
#include <stdio.h>
#include <pthread.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
int g_data=0;
pthread_mutex_t mutex; //定义全局变量 互斥量
void * attr(void *arg)
{
int i=0;
pthread_mutex_lock(&mutex); //上锁 参数为锁的地址
for(i=0;i<5;i++){//上锁后的t1运行5次后解锁
printf("t1:%ld pthread is create\n",(unsigned long)pthread_self());
printf("t1:data is %d\n",*((int*)arg));
sleep(1);
}
pthread_mutex_unlock(&mutex); //解锁
}
void * attr1(void *arg)
{
pthread_mutex_lock(&mutex);
printf("t2:%ld pthread is create\n",(unsigned long)pthread_self());
printf("t2:data is %d\n",*((int*)arg));
pthread_mutex_unlock(&mutex);
}
void * attr2(void *arg)
{
pthread_mutex_lock(&mutex);
printf("t3:%ld pthread is create\n",(unsigned long)pthread_self());
printf("t3:data is %d\n",*((int*)arg));
pthread_mutex_unlock(&mutex);
}
int main()
{
int ret;
int data=100;
pthread_t t1;
pthread_t t2;
pthread_t t3;
pthread_mutex_init(&mutex,NULL); //将互斥锁初始化,第一个参数为指针 指向互斥锁地址
ret=pthread_create(&t1,NULL,attr,(void *)&data);
if(ret==0){
printf("main:create t1 pthread success\n");
}
ret=pthread_create(&t2,NULL,attr1,(void *)&data);
if(ret==0){
printf("main:create t2 pthread success\n");
}
ret=pthread_create(&t3,NULL,attr2,(void *)&data);
if(ret==0){
printf("main:create t3 pthread success\n");
}
printf("main:the pthread is %ld\n",(unsigned long)pthread_self());
//int pthread_join(pthread_t thread, void **rval_ptr);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
pthread_mutex_destroy(&mutex);//程序结束销毁互斥锁
return 0;
}
补充:死锁
死锁 (deallocks): 是指两个或两个以上的进程(线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程(线程)称为死锁进程(线程)。
产生死锁的四个必要条件
互斥条件:一个资源每次只能被一个进程(线程)使用;
请求与保持条件:一个进程(线程)因请求资源而阻塞时,对已获得的资源保持不放;
不剥夺条件 : 此进程(线程)已获得的资源,在末使用完之前,不能强行剥夺;
循环等待条件 : 多个进程(线程)之间形成一种头尾相接的循环等待资源关系;
五、条件
条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。
条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。
条件变量使用之前必须首先初始化,pthread_cond_t数据类型代表的条件变量可以用两种方式进行初始化,可以把常量PTHREAD_COND_INITIALIZER赋给静态分配的条件变量,但是如果条件变量是动态分配的,可以使用pthread_cond_destroy函数对条件变量进行去除初始化(deinitialize)。
创建及销毁条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
//第一个参数 为cond条件的地址,第二个为属性 默认写NULL
int pthread_cond_destroy(pthread_cond_t cond);
// 返回:若成功返回0,否则返回错误编号
触发 等待
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
//cond 地址 mutex地址
int pthread_cond_signal(pthread_cond_t cond);
例子
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
//int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
int g_data=0;
pthread_mutex_t mutex;
pthread_cond_t cond;
void * attr(void *arg)
{
printf("t1:%ld pthread is create\n",(unsigned long)pthread_self());
printf("t1:data is %d\n",*((int*)arg));
while(1){
pthread_cond_wait(&cond,&mutex); //cond阻塞直至收到signal信号才释放
printf("t1 run===================\n");
printf("t1:g_data=%d\n",g_data);
g_data=0;
sleep(1);
}
}
void * attr1(void *arg)
{
printf("t2:%ld pthread is create\n",(unsigned long)pthread_self());
printf("t2:data is %d\n",*((int*)arg));
while(1){
printf("t2:g_data=%d\n",g_data);
pthread_mutex_lock(&mutex);
g_data++;
if(g_data==3){
pthread_cond_signal(&cond);
//t2data打印3次 发信号给t1
}
pthread_mutex_unlock(&mutex);
sleep(1);
}
}
int main()
{
int ret;
int data=100;
pthread_t t1;
pthread_t t2;
pthread_mutex_init(&mutex,NULL);
pthread_cond_init(&cond,NULL);
ret=pthread_create(&t1,NULL,attr,(void *)&data);
if(ret==0){
}
ret=pthread_create(&t2,NULL,attr1,(void *)&data);
if(ret==0){
}
//printf("main:the pthread is %ld\n",(unsigned long)pthread_self());
//int pthread_join(pthread_t thread, void **rval_ptr);
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}

测试脚本
int main(int argc,char**argv)
{
int i=0;
int time=atoi(argv[1]);
for(i=0;i<time;i++){
system("./demo8");
}
}
./a.out 10 >>test.ret.text &
本文深入探讨了线程在Linux系统中的概念,比较了进程与线程的区别,包括实现方式、设计模式、通信与控制方式,以及互斥锁和条件变量的使用实例。还介绍了线程的创建、退出与等待方法,以及如何处理死锁问题和条件变量的触发等待。
1353

被折叠的 条评论
为什么被折叠?



