线程共享的东西
- 同一块地址空间
- 文件描述符表
- 每种信号的处理方式(如: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;失败返回错误码
作用是主线程等待新线程退出,否则就会导致进程的内存泄漏 。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源
类似进程的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 命名空间
pthread_detach
线程同步:
常见的方法:互斥锁,条件变量,读写锁,信号量
互斥量: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_mutex_timelock
1.初始化 pthread_mutex_init
2.加锁解锁,参数为绝对时间pthread_mutex_timelock(,绝对时间)结构是timespec ,clock_gettime()函数获取
3.注销pthread_mutex_destroy
互斥量属性
用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_wait之前,调pthread_mutex_lock上锁。在pthread_cond_wait之后,调pthread_mutex_unlock解锁。
激发
激活一个等待该条件的线程 :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 : 用于单个进程的多线程同步。这是默认的情况。
读写锁 pthread_rwlock_t
读写锁比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_rwlock_timedwrlock(,绝对时间) 结构是timespec ,clock_gettime()函数获取
读写锁的属性
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 : 用于单个进程的多线程同步。这是默认的情况。
说明了互斥锁、条件变量、读写锁可在不同的进程间共享,而不是只在单个进程内的不同线程间共享。
信号量
信号量比互斥量使用范围更广,可以同时支持多个线程或进程间同步。
信号量(semaphore)是一个计数器,信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)
所拥有。信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明
它被占用,测试的线程要进入睡眠队列中,等待被唤醒。
互斥锁必须总是由锁住它的线程解锁,信号量的挂出却不必由执行它的等待操作的同一线程执行。说明信号量是互斥锁的升级。
P操作:这个操作会将信号量减一,相减后信号量如果小于0,则表示资源已经被占用了,进程需要阻塞等待;如果大于等于0,则说明还有资源可用,进程可以正常执行。
V操作:这个操作会将信号量加一,相加后信号量如果小于等于0,则表明当前有进程阻塞,于是会将该进程唤醒;如果大于0,则表示当前没有阻塞的进程。
当多个线程在等待同一个信号量时,不能预测V操作会重启哪一个线程。
分为
1.二元信号量 和互斥类似
2.计数信号量 :posix信号量 的shm_open
3.计数信号量集: system v信号量 shmget
有posix信号量 和 system v信号量
(都属于 用户态进程使用的信号量)
posix 信号量有 2种
1.posix 有名信号量:使用posix ipc 名字标识,其值保存在文件中,可用于进程或线程间的同步。彼此无亲缘关系的不同进程通常使用有名信号量。
2.posix基于内存的信号量:即无名信号量,其值保存内存区中,可用于有亲缘关系的进程或线程间的同步。所以无名信号量如果不是放在进程间的共享内存区中,是不能用来进行进程间同步的,只能用来进行线程同步。
互斥锁,信号量和条件变量的区别:
互斥锁强调的是资源的访问互斥,信号量强调进程或者线程之间的同步,条件变量常与互斥锁同时使用,达到线程同步的目的:条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足。
互斥锁总是必须由给其上锁的线程解锁,信号量的挂出确不必由执行过它的等待操作的同一线程执行;另外条件变量和信号量一样。
互斥锁要么被锁住,要么被解锁(二值状态,类似于二值信号量);
既然信号量有一个与之关联的状态(它的数值),那么信号量的挂出操作总是被记住。然而当向一个条件变量发送信号时,如果没有线程等待在该条件变量上,那么信号将丢失
对象 | 操作 | 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 |