linux 线程的基本知识
线程共享的东西
- 同一块地址空间
- 文件描述符表
- 每种信号的处理方式(如:SIG_DFL,SIG_IGN或者自定义的信号优先级)
- 当前工作目录
- 用户id和组id
线程独立的东西
- 线程会产生临时变量,临时变量保存再栈上,所以每个线程都有自己的私有栈结构
- 每个线程都有私有的上下文信息。
- 线程ID
线程的优点:
- 1. 提高程序并发性
- 2. 开销小
- 3. 数据通信、共享数据方便
常用的函数
需引用头文件 #include<pthread.h>
一般以“pthread_”打头
创建线程 :
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
参数
thread:返回线程ID
attr:设置线程的属性,attr为NULL表示使用默认属性
start_routine:是个函数地址,线程启动后要执行函数。该函数运行结束,则线程结束
arg:传给线程启动函数的参数,如要传多个参数, 可以用结构封装
返回值:成功返回0;失败返回错误码
对应进程的创建新进程为fork();
获取线程id :
pthread_self()
对应进程的获取进程id为getpid().
终止线程:有3种办法
1.return返回 .最正常的方式
2.pthread_exit(void *val) 参数val表示退出码 一般是自身强行退出。
3.pthread_cancel(pthread_t thread) 取消线程 可以自己把自己杀死,也可以杀死别人 成功返回0;失败返回错误码 相当于进程的kill
线程等待
int pthread_join(pthread_t thread,void **retval)
thread:线程ID
value_ptr:它指向一个指针,后者指向线程的返回值 即如pthread_exit的参数。
返回值:成功返回0;失败返回错误码
作用是主线程等待新线程退出,否则就会导致进程的内存泄漏 。类似进程的waitpid()函数。
阻塞函数,直到指定的线程调用pthread_exit 、从启动例程返回或被取消。
如果从启动例程返回,retval表示返回码。如果是被取消,retval被置为PTHREAD_CANCELED.
分离线程:
如果新线程创建后,不用pthread_join()等待回收新线程,那么就会造成内存泄漏,但是当等待新线程时,主线程就会一直阻塞,影响主线程处理其他链接要求,这时候就需要一种办法让新线程退出后,自己释放所有资源,因此产生了线程分离。
线程自己退出后释放自己资源: int pthread_detach(pthread_self())
线程组内其他线程对目标线程进行分离:int pthread_detach(pthread_t thread)
返回值:成功返回0,失败返回错误码
一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
linux线程没有直接的挂起和恢复函数,需要通过互斥锁和条件变量来间接实现。
进程 线程
fork pthread_create
exit pthread_exit
waitpid pthread_join
kill pthread_cancel
getpid pthread_self 命名空间
线程同步:
常见的方法:互斥锁,条件变量,读写锁,信号量
互斥锁:mutex
创建:有静态和动态两种方式
静态方式: pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
动态方式: int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) 其中mutexattr用于指定互斥锁属性,如果为NULL则使用缺省属性。
注销: int pthread_mutex_destroy(pthread_mutex_t *mutex) 销毁一个互斥锁即意味着释放它所占用的资源,且要求锁当前处于开放状态。
锁操作:
加锁:int pthread_mutex_lock(pthread_mutex_t *mutex) 阻塞
解锁:int pthread_mutex_unlock(pthread_mutex_t *mutex) 阻塞
int pthread_mutex_trylock(pthread_mutex_t *mutex), 与pthread_mutex_lock()类似,不同的是在锁已经被占据时返回EBUSY而不是挂起等待。
相当于windows的临界区关键段
互斥锁必须总是由锁住它的线程解锁。
互斥量属性 用pthread_mutexattr_t 结构表示
初始化
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
反初始化
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
进程共享属性
//获取属性
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared);
//修改属性
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
pshared :
1.为PTHREAD_PROCESS_SHARED:该互斥量就可以用于进程间的同步。
2.为PTHREAD_PROCESS_PRIVATE : 用于单个进程的多线程同步。这是默认的情况。
条件变量
是一种“事件通知机制”,等价于windows平台的事件量 createevent. 和waitforsingleobject
条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。为了防止竞争,条件变量的使用总是和一个互斥锁结合在一起。
创建:
静态:pthread_cond_t cond=PTHREAD_COND_INITIALIZER
动态:int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr) cond_attr值通常为NULL,且被忽略
销毁
int pthread_cond_destroy(pthread_cond_t *cond)
等待
阻塞等待:int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
超时等待:int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime) 如果在 给定时刻前条件没有满足,则返回ETIMEOUT,结束等待
分解pthread_cond_wait的动作为以下几步:
1,线程放在等待队列上,解锁
2,等待 pthread_cond_signal或者pthread_cond_broadcast信号之后去竞争锁
3,若竞争到互斥索则加锁。
所以等待条件变量总是返回被锁住的互斥量,即在pthread_cond_wait或pthread_cond_timedwait 函数返回时,mutex是锁住状态。
激发
激活一个等待该条件的线程 :pthread_cond_signal()
激活所有等待线程:pthread_cond_broadcast()
原型:pthread_cond_signal(pthread_cond_t *cond)
条件变量的属性 用pthread_condattr_t结构表示
初始化
pthread_condattr_init(pthread_condattr_t* attr);
反初始化
pthread_condattr_destroy(pthread_condattr_t* attr);
进程共享属性
//获取属性
int pthread_condattr_getpshared(const pthread_condattr_t *restrict attr, int *restrict pshared);
//修改属性
int pthread_condattr_setpshared(pthread_condattr_t *attr, int pshared);
pshared :
1.为PTHREAD_PROCESS_SHARED:该互斥量就可以用于进程间的同步。
2.为PTHREAD_PROCESS_PRIVATE : 用于单个进程的多线程同步。这是默认的情况。
读写锁
读写锁比mutex有更高的适用性,可以多个线程同时占用读模式的读写锁,但是只能一个线程占用写模式的读写锁。
1. 当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞;
2. 当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行枷锁的线程将阻塞;
3. 当读写锁在读模式锁状态时,如果有另外线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁请求,这样可以避免读模式锁长期占用,而等待的写模式锁请求长期阻塞;
创建
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); attr默认为null.
销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
读加锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
写加锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
解锁
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
都是阻塞操作
阻塞操作
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
读写锁的属性 pthread_rwlockattr_t
初始化
int pthread_rwlockattr_init(pthread_rwlockattr_t* attr);
反初始化
int pthread_rwlockattr_destroy(pthread_rwlockattr_t* attr);
进程共享属性
//获取属性
int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *restrict attr, int *restrict pshared);
//修改属性
int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);
pshared :
1.为PTHREAD_PROCESS_SHARED:该互斥量就可以用于进程间的同步。
2.为PTHREAD_PROCESS_PRIVATE : 用于单个进程的多线程同步。这是默认的情况。
说明了互斥锁、条件变量、读写锁可在不同的进程间共享,而不是只在单个进程内的不同线程间共享。
///下面的信号量属于进程间通信的
信号量
互斥锁必须总是由锁住它的线程解锁,信号量的挂出却不必由执行它的等待操作的同一线程执行。说明信号量是互斥锁的升级
有posix信号量 和 system v信号量
posix 信号量有 2种
1.posix 有名信号量:使用posix ipc 名字标识,可用于进程或线程间的同步。彼此无亲缘关系的不同进程需要使用信号量,通常使用有名信号量。
2.posix基于内存的信号量:存放共享内存区中,可用于进程或线程间的同步。即无名信号量。而无名信号量如果不是放在进程间的共享内存区中,是不能用来进行进程间同步的,只能用来进行线程同步。
有名的信号量
#include "semaphore.h"
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
功能:创建或打开一个信号量
参数说明:name:信号量的名字,标识信号量;
可用函数px_ipc_name, 原型char *px_ipc_name(const char *name)
oflag参数可以为:0,O_CREAT,O_CREAT | O_EXCL。
如果为0表示打开一个已存在的信号量,
如果为O_CREAT,表示如果信号量不存在就创建一个信号量,如果存在则打开被返回。此时mode和value需要指定。
如果为O_CREAT | O_EXCL,表示如果信号量已存在会返回错误。
mode参数用于创建信号量时,表示信号量的权限位,和open函数一样包括:S_IRUSR,S_IWUSR,S_IRGRP,S_IWGRP,S_IROTH,S_IWOTH。如可以是0666. 如果指定了O_CREAT标志,则mode、value是需要的。
value:信号量的初始值,一般是1. 只有当所需的信号量尚未存在时才初始化。可以理解为如果存在信号量了,该值无效
该函数成功后,会在/dev/shm下生成一个sem.xxx的文件。xxx表示name。
int sem_close(sem_t *sem);
功能:sem_close用于关闭打开的信号量。当一个进程终止时,内核对其上仍然打开的所有有名信号量自动执行这个操作。
个人自己见解:相当于计数减1.但就算计数减到0,也不能自动删除信号量,需要调用sem_unlink()才能删除(前提是计数已为0)。可以通过sem_close函数或进程终止办法使计数减1。
返回值:成功返回0,失败返回-1
调用sem_close关闭信号量并没有把它从系统中删除它,POSIX有名信号量是随内核持续的。即使当前没有进程打开某个信号量它的值依然保持。直到内核重新自举或调用sem_unlink()删除该信号量。
int sem_unlink(const char *name);
功能:将有名信号量立刻从系统中删除,但信号量的销毁是在所有进程都关闭信号量的时候(只close是不够的!)也就是要等待最后一个sem_close发生。
返回值:成功返回0,失败返回-1
sem_unlink会马上删除指定的信号量名,但要等到所有打开该信号量的进程关闭该信号量后才删除该信号。详细地说,当进程创建一个有名信号量,会在/dev/shm下生成一个sem.xxx的文件,所有打开该信号量的进程(包括创建它的进程)都会增加该文件的引用计数,并且这个计数由内核管理。当调用sem_unlink时,/dev/shm下的sem.xxx文件会马上被删除,但是信号量本身并没有被删除,所有已打开该信号量的进程仍能正常使用它。直到所有打开该信号量的进程关闭该信号量后,内核才真正删除信号量。个人见解:所以应该只让一个进程调用sem_unlink函数,其他每个进程调用sem_close函数计数减1.
int sem_trywait(sem_t *sem);
int sem_wait(sem_t *sem);
int sem_timedwait(sem_t * sem, const struct timespec time);
sem_wait :该操作会检查信号量的值,如果其值小于或等于0,那就阻塞,直到该值变成大于0,然后等待进程将信号量的值减1,进程获得共享资源的访问权限。这整个操作必须是一个原子操作
sem_trywait为非阻塞版本,当信号量为0时,该函数返回-1并且将errno置为EAGAIN
sem_timedwait带有超时功能,超时到期并且信号量计数没能减1,sem_timedwait将返回-1且将errno设置为ETIMEDOUT。(毕竟一直阻塞也不是办法。。
int sem_post(sem_t *sem);
该操作将信号量的值加1,如果有进程阻塞着等待该信号量,那么其中一个进程将被唤醒。该操作也必须是一个原子操作。
int sem_t getvalue(sem_t sem, int *val);
获取信号量的值,一般只用来调试。
无名信号量
二. 无名信号量
头文件: <semaphore.h>
int sem_init(sem_t *sem, int pshared,unsigned value);
功能:初始化指定的信号量
返回值:成功 0 ; 错误 错误号。
参数说明:sem:信号量变量名;参数value为信号量的初始值。
参数pshared用于说明信号量的共享范围,如果pshared为0,那么该信号量只能由初始化这个信号量的进程中的线程使用,如果pshared非零,任何可以访问到这个信号量的进程都可以使用这个信号量。
必须保证只调用sem_init()进行初始化一次,对于一个已初始化过的信号量调用sem_init()的行为是未定义的
int sem_destroy(sem_t *sem);
功能:销毁一个指定的信号量
返回值:成功 0 ; 错误 错误号。
参数说明:sem:信号量变量名(sem_init内值)
摧毁一个有线程阻塞在其上的信号量的行为是未定义的。
其他的操作,如等待、挂出、获取信号量值都是和有名信号量一样的。
信号量和互斥量,条件变量的区别
互斥量必须由给它上锁的线程解锁。而信号量不需要由等待它的线程进行挂出,可以在其他进程进行挂出操作。
互斥量要么被锁住,要么是解开状态,只有这两种状态。而信号量的值可以支持多个进程成功进行wait操作。
信号量的挂出操作总是被记住,因为信号量有一个计数值,挂出操作总会将该计数值加1,然而当向条件变量发送一个信号时,如果没有线程等待在条件变量,那么该信号会丢失。
继承:
对于有名信号量在父进程中打开的任何有名信号量在子进程中仍是打开的。个人见解是:还是同一个信号量,连计数都没有增加。
对于无名信号量的继承要根据信号量在内存中的位置:
- 如果无名信号量是在单个进程内部的数据空间中,那么信号量就是进程数据段或者是堆栈上,当fork产生子进程后,该信号量只是原来的一个拷贝,和之前的信号量是独立的。
- 如果无名信号量位于不同进程的共享内存区,那么fork产生的子进程中的信号量仍然会存在该共享内存区,所以该信号量仍然保持着之前的状态。
system v信号量
1.key_t 键:至少32位整数,使用ftok
key_t ftok(const char *pathname, int proj_id)
把从pathname导出的信息与id的低序8位组合成一个整数IPC键
ipc 标识符
① 调用ftok,给它传递pathname和proj_id,操作系统根据两者合成key值。
② 指定key为IPC_PRIVATE,内核保证创建一个新的、唯一的IPC对象,IPC标识符与内存中的标识符不会冲突。IPC_PRIVATE为宏定义,其值等于0。
③ 指定key为大于0的常数,这需要用户自行保证生成的IPC key值不与系统中存在的冲突,而前两种是操作系统保证的。
int semget(key_t key, int nsems, int semflg)
参数key是一个键值,由ftok获得,唯一标识一个信号灯集.
参数nsems指定信号灯集包含信号灯的数目; 如果不创建新的信号量集,只访问已存在的,则该参数应为0
semflg参数是一些标志位。IPC_CREAT、IPC_EXCL、SEM_R 、SEM_A等。
该调用返回与健值key相对应的信号灯集id 调用返回:成功返回信号灯集描述字,否则返回-1。
nt semop(int semid, struct sembuf *sops, unsigned nsops);
semid是信号灯集ID,
sops数组的每一个sembuf结构都刻画一个在特定信号灯上的操作。
nsops为sops数组的大小
sem_flg可取IPC_NOWAIT以及SEM_UNDO两个标志
int semctl(int semid,int semnum,int cmd,union semun arg)
该系统调用实现对信号灯的各种控制操作,
参数semid指定信号灯集,
参数cmd指定具体的 操作类型;
参数semnum指定对哪个信号灯操作,
常见的cmd命令
IPC_STAT 获取信号灯信息
IPC_SET 设置信号灯信息
GETVAL 返回semnum所代表信号灯的值
对象 | 操作 | Linux Pthread API | Windows SDK 库对应 API |
---|---|---|---|
线程 | 创建 | pthread_create | CreateThread |
退出 | pthread_exit | ExitThread terminatethread | |
等待 | pthread_join | WaitForSingleObject | |
互斥锁 | 创建 | pthread_mutex_init | CreateMutex |
销毁 | pthread_mutex_destroy | CloseHandle | |
加锁 | pthread_mutex_lock | WaitForSingleObject | |
解锁 | pthread_mutex_unlock | ReleaseMutex | |
条件 | 创建 | pthread_cond_init | CreateEvent |
销毁 | pthread_cond_destroy | CloseHandle | |
触发 | pthread_cond_signal | SetEvent | |
广播 | pthread_cond_broadcast | SetEvent / ResetEvent | |
等待 | pthread_cond_wait / pthread_cond_timedwait | SingleObjectAndWait |
linux 线程的基本知识相关推荐
- Linux线程操作以及相关知识
1 线程 ◼ 与进程(process)类似,线程(thread)是允许应用程序并发执行多个任务的一种机 制.一个进程可以包含多个线程.同一个程序中的所有线程均会独立执行相同程序,且共 享同一份全局内存 ...
- [转载]Linux 线程实现机制分析
自从多线程编程的概念出现在 Linux 中以来,Linux 多线应用的发展总是与两个问题脱不开干系:兼容性.效率.本文从线程模型入手,通过分析目前 Linux 平台上最流行的 LinuxThreads ...
- Linux 线程的创建与同步
Linux 线程的创建与同步 1.线程的定义 2.线程的创建和使用 3.理解线程的并发运行 3.线程同步 3.线程的实现 1.线程的定义 线程:进程内部的一条执行路径.是资源调度和执行的基本单位. 进 ...
- linux 线程 进程经典文章
进程是程 序在计算机上的一次执行活动.当你运行一个程序,你就启动了一个进程.显然,程序是 死的(静态的),进程是活的(动态的).进程可以分为系统进程和用户进程.凡是用于完成操作系统的各种功能的进程就是 ...
- linux 线程--内核线程、用户线程实现方法
Linux上进程分3种,内核线程(或者叫核心进程).用户进程.用户线程 内核线程拥有 进程描述符.PID.进程正文段.核心堆栈 当和用户进程拥有相同的static_prio 时,内核线程有机会得到更多 ...
- Linux 线程实现机制分析
本文转自:http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 一.基础知识:线程和进程 按照教科书上的定义,进程是资源管理的最小单位 ...
- Linux 线程实现机制分析--转
http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 一.基础知识:线程和进程 按照教科书上的定义,进程是资源管理的最小单位,线程是程 ...
- 在Linux系统下实现进程,Linux进程学习(一)之Linux进程的基本知识和实现
最近一周学习了Linux 进程编程的知识,现对其总结如下. 在第一部分中我们先对进程的基本概念以及在Linux 中是如何来现实进程的进行介绍 Tiger-John说明 : 许多人在学习中只注重如何编程 ...
- c++ linux 线程等待与唤醒_Linux驱动程序基石-POLL机制(附.视频)
今天<升级版全系列嵌入式视频_入门篇>新增一节视频:19.2_POLL机制 时长24分钟,免费观看 何为POLL机制? 给驱动程序加一个闹钟,让APP不必死等数据: 既可以快速掌握 POL ...
- 【嵌入式Linux】嵌入式Linux应用开发基础知识之多线程编程
文章目录 前言 1.多线程基础编程--创建线程和使用等待函数休眠线程 1.1.程序分析--使用信号量PV操作sem_wait 1.2.程序分析--使用条件变量pthread_cond_wait 2.一 ...
最新文章
- 碰到故障大全---cd
- 误删除的文件夹还能恢复吗?
- CSS知识总结之设计模式(持续学习中)
- Jupyter Notebook 常用的快捷键
- 开源开放 | 熵简科技 AI Lab 开源金融领域中文预训练语言模型 FinBERT
- 「超级右键」Mac必备的一款软件,新手get!
- android实现欢迎启动界面
- 推荐系统学习(二)基于用户/物品的协同过滤算法(User-CF / Item-CF)
- --6、专业信息表(表)
- 学习NSURLSession(1)
- 微信小程序富文本编辑器 editor 组件源码
- kindle刷机ttl_kindle变砖修复及刷机
- 计算机test的应用,例举内存检测工具memtest详细使用教程
- 【笔记】操作系统题目整理
- 无线网服务器拒绝连接,网络拒绝连接什么原因
- 在线考试防止切屏功能
- 菜鸟学JAVA之——多线程
- 阐明iOS证书和provision文件
- 51单片机数码管静态显示
- 梅特勒托利多电子秤显示EEP服务器错误,托利多电子秤TCII故障维修方法(一)...