【Linux】线程基本知识概述
来源:互联网 发布:apache artemis 编辑:程序博客网 时间:2024/05/16 07:37
本文内容概述:
1.线程的基本概念(包括线程的定义,线程之间的共享资源和私有资源);
2.基本函数(包括线程的创建,终止,等待,可分离和可切换,当然其中还会涉及互斥锁方面的内容等等)。
1.线程的基本概念:
在前边的学习中,我们知道,进程是在各自独立的地址空间中运行,如果需要共享资源,则要进行进程间的通信,比如管道,消息队列,信号量,共享内存这些 ,所以完成通信是比较困难的。而线程是进程内的一个执行分支,顾名思义,线程是在进程内部执行,占用着进程的地址空间,所以,线程之间的通信是比较容易的---定义一个公共的buf,然后就能直接进行通信。
在Linux系统下,是没有线程 这个概念的,都是用进程去模拟线程。
我们看到的进程有可能是线程,所以,我们将线程称为轻量级进程。
由于线程是进程内部的执行流,所以线程就会共享一些进程的信息,当然也会有自己私有的信息:
线程共享进程的以下资源:
1)文件描述符信息;
2)每种信号的处理方式(由于线程是进程内部的执行流,所以每种信号的处理方式也是共享的);
3)当前工作目录;
4)各种id信息,比如用户id和组id信息。
线程也会有自己私有的信息:
1)线程id;
2)线程的上下文信息(每个线程都必须有自己的上下文信息,存储在自己的PCB中,当然有些操作系统中会 有一种存储线程信息的TCB结构体)
3)栈空间
4)调度优先级
5)errno变量
在Linux下的C api函数发生异常时,一般会将errno变量(在errno.h文件中)赋予一个整形值,不同的值代表不同的含义,可以通过输出的值来查看出错的信息。
比如:printf("%d",errno);就可以输出异常时的错误码
6)信号屏蔽字信息,这里的 信号屏蔽字就有点类似于文件的权限信息中的umask。
2.基本函数:
(1)线程的创建
(2)线程的终止:
线程的终止方法是有3种:
a.直接在线程函数中return,但是在main函数中return,是表示整个进程的终止;
b.采用pthread_cancel函数让同一进程中的其他的线程去终止;
程序举例:
#include<stdio.h>#include<pthread.h>#include<unistd.h>void* pthreadRun(void* arg){ int count = 3; while(count--) { printf("new pthread,tid:%lu,pid:%d\n",pthread_self(),getpid()); }}int main(){ pthread_t tid = 0; int ret = pthread_create(&tid,NULL,pthreadRun,NULL); if(ret != 0) { perror("pthread_create"); return -1; } if(ret != 0) { perror("pthread_detach"); return -1; } sleep(1); if(pthread_cancel(tid)!= 0) { printf("cancel failed\n"); return 1; } int exitCode; pthread_join(tid,(void**)&exitCode); printf("new pthread exit code:%d\n",exitCode); return 0;}
这段程序的运行结果是:
解释: 由于在主线程中已经sleep了1秒之后,才终止新线程的,由于在1秒之内,新线程早已运行完成,所以,就会终止失败。
如果将终止新线程之前的sleep去掉,程序的运行结果究竟是什么呢?
解释:新线程没有执行完成,就被主线程终止了,此时是终止成功的,所以退出码就是-1.
c.使用pthread_exit()自己终止自己。
(3)线程的等待:
(4)线程的可分离性:
一个线程是可结合的或者是可分离的。一个可结合的线程可以被与之在同一个进程中的其他线程所终止 ;而一个可分离的线程不需要主线程的等待,它的存储器等资源会在它运行结束时被操作系统回收。我们都知道,线程是进程内部的一个执行分支,为什么这么说?因为进程内的线程共享一个地址空间,所以在被分离的线程没有执行完成时,主线程不要退出,否则新的线程无法继续执行。主线程不可以等待被分离的线程。
程序举例:
#include<stdio.h>#include<pthread.h>#include<unistd.h>void* pthreadRun(void* arg){ int count = 3; while(count--) { printf("new pthread,tid:%lu,pid:%d\n",pthread_self(),getpid()); sleep(1); }}int main(){ pthread_t tid = 0; int ret = pthread_create(&tid,NULL,pthreadRun,NULL); if(ret != 0) { perror("pthread_create"); return -1; } usleep(1000); ret = pthread_detach(tid); if(ret != 0) { perror("pthread_detach"); return -1; } if(pthread_cancel(tid)!= 0) { printf("cancel failed\n"); return 1; } int exitCode; if(0 == pthread_join(tid,(void*)&exitCode)) { printf("wait success.\n"); printf("new pthread exit code:%d\n",exitCode); } else { printf("wait failed\n"); } return 0;}程序运行结果:
解释:从运行结果我们可以看出,我们不可以等待已经被分离的线程,但是可以终止已经分离的线程。
(5)线程的切换:
线程切换的条件:
a.一个时间片的完成;
b.操作系统模式的切换(内核态向用户态的切换)。
时间片的结束,会进行线程的切换,这个是比较容易理解的。
为什么操作系统模式的切换就会进行线程的切换?因为线程(在linux系统下,线程也被认为是进程,也有自己的PCB)的PCB是在内核中,只有内核态(也就是系统)才可以进行查看线程的信息,即就是访问PCB。我们知道,系统调用都是在内核态进行完成的,printf函数的底层调用的就是系统调用,所以我们采用printf函数进行显示数据的时候,就可能完成线程的切换。
所以,写一个程序,制造线程的切换。
#include<stdio.h>#include<pthread.h>#include<unistd.h>static int count = 0;void* pthreadRun(void* arg){ int val = 0; int i = 0; while(i < 5000) { val = count; printf("new pthread,tid:%lu,pid:%d,count:%d\n",pthread_self(),getpid(),count); count = val + 1; i++; } return NULL;}int main(){ pthread_t tid1 = 0; pthread_t tid2 = 0; int ret = pthread_create(&tid1,NULL,pthreadRun,NULL); if(ret != 0) { perror("pthread_create"); return -1; } ret = pthread_create(&tid2,NULL,pthreadRun,NULL); if(ret != 0) { perror("pthread_create"); return -1; } pthread_join(tid1,NULL); pthread_join(tid2,NULL); printf("count:%d\n",count); return 0;}
运行结果介于5000到10000之间不等。
如果我们给临界资源加锁,运行完代码之后解锁。代码如下:
#include<stdio.h>#include<pthread.h>#include<unistd.h>static int count = 0;pthread_mutex_t myLock = PTHREAD_MUTEX_INITIALIZER;void* pthreadRun(void* arg){ int val = 0; int i = 0; while(i < 3000) { pthread_mutex_lock(&myLock); val = count; printf("new pthread,tid:%lu,pid:%d,count:%d\n",pthread_self(),getpid(),count); count = val + 1; i++; pthread_mutex_unlock(&myLock); } return NULL;}int main(){ pthread_t tid1 = 0; pthread_t tid2 = 0; int ret = pthread_create(&tid1,NULL,pthreadRun,NULL); if(ret != 0) { perror("pthread_create"); return -1; } ret = pthread_create(&tid2,NULL,pthreadRun,NULL); if(ret != 0) { perror("pthread_create"); return -1; } pthread_join(tid1,NULL); pthread_join(tid2,NULL); pthread_mutex_destroy(&myLock); printf("count:%d\n",count); return 0;}
这样对临界资源进行加锁之后,就可以保证全局变量最终可以加至6000.
关于互斥锁的函数的声明:
1)初始化和销毁函数
为什么加锁就可以防止线程之间相互干扰?
那是因为加锁就可以保证锁之间的代码是一个原子操作(要么执行就能执行完,要么不执行,也就是非0即1的状态)。
那么lock()和unlock()函数是如何实现的呢?
方法一:
解释:我们知道mutex = 0;这一步并不是原子操作,如果两个线程同时调用lock函数,都判断出mutex > 0,然后其中一个线程将mutex置为0,另一个线程并不知道此事,也将mutex置为0,于是两个线程都以为自己获得了锁资源,这样就发生了冲突。所以这种办法是不可行的。
执行赋值操作需要几步呢?
步骤一:将mutex的数据从内存读到寄存器;
步骤二:通过CPU内的运算器将mutex进行赋值;
步骤三:将mutex的值重新写回内存。
方法二:
解释:由于方法一中的赋值操作不是原子操作,导致上述方法不可取。为了解决这个问题,大多体系结构提供了swap或者exchange命令,将寄存器和内存里的值进行交换,就保证了原子操作。
lock函数 语句解释:
将al寄存器中的值放置为0;
将al寄存器和内存中的mutex的值进行交换;
......
如果执行了xchgb指令之后,又有一个线程申请锁资源,此时它看到的mutex就是0,表示不可以申请到资源;
如果执行了movb指令之后,又有一个线程申请锁资源,此时哪个线程获得锁资源,就看自己的优先级或者权限等等问题了。
所以,这个方法就是lock函数和unlock函数里执行的操作了。
关于线程的基本知识就先整理到这里~~
4 0
- 【Linux】线程基本知识概述
- Linux线程基本知识
- linux 线程概述
- Linux线程模型概述
- 【Linux基础】线程概述
- Linux下线程概述
- Linux线程同步概述
- Linux 线程(1):线程概述
- 线程基本知识
- 线程基本知识
- Linux进程与线程概述
- LINUX进程与线程概述
- Linux进程与线程概述
- [GNU/Linux] Linux系统调用-线程相关(一):基本知识
- 【基本知识】Java概述
- jdbc基本知识概述
- 操作系统基本知识概述
- linux基本知识
- 新人报道
- git管理代码时,不显示直接拖进去的工程
- 1052. Linked List Sorting
- Linux find命令用法小结
- 谈谈关于内存对齐与补齐
- 【Linux】线程基本知识概述
- SolrCloud的搭建过程
- 开放才能进步!Angular和Wijmo一起走过的日子
- Mysql-win系统密码破解之方法
- 1054. The Dominant Color
- 数据处理函数
- Java集合框架详解之一点小总结
- Android 进程&多线程
- 1055. The World's Richest