多线程与线程同步

线程概念

线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
优点:

  1. 通过为每种事件类型分配单独的处理线程,可以简化处理异步事件的代码。每个线程在进行事件处理时可以采用同步处理模式,同步编程比异步编程简单的多。
  2. 多个进程必须使用OS提供的复杂机制才能实现内存和文件描述符的共享,而多个线程可自动的访问相同的地址空间和文件描述符。可以很容易的完成共享。
  3. 可以提高程序的吞吐量
  4. 交互的程序使用多线程来改善响应时间,多线程看而已把程序中用户输入的部分和其他部分分开。
    每个线程都包含有表示执行环境变量所需要的信息,其中包括进程中识别线程的线程ID,一组寄存器值,栈,调度优先级可策略、信号屏蔽字,errno变量以及线程私有数据。一个进程的所有信息对该进程的所有线程都是共享的,包括可执行的代码、程序的全局内存和堆内存,栈及文件描述符。

    线程标识、创建与终止

线程标识:

和进程一样,每个线程都有一个线程ID。进程ID在整个系统是唯一的,但是线程不同,线程ID在它所属的进程上下文才有意义。
线程ID用pthread_t数据类型来标识的,是新的时候可以用一个结构体来标识pthread_t的数据类型,于是不能用“==”来判断是否为同一个线程。所以就用一个函数来进行比较

#include <pthread.h>
int pthread_equal(pthread_t pid1, pthread_t pid2);

与进程一样,线程也可以获得自己的线程ID,函数表示如下

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

线程创建:

在传统进程模型中,每个进程只有一个线程,所以线程和进程在行为上是没有什么区别的。新增的线程可以用pthread_create函数创建

#include <pthread.h>
int pthread_create(pthread_t *tpid,const pthread_attr_t
*attr,void *(*start_run)(void *),void *arg);
//成功返回0,否则返回错误编号

tpid是pthread_t类型的指针
attr结构体参数用于定制各种不同的属性,如果attr为NULL则表示创建的线程使用默认属性。
*strat_run是一个函数指针,指向线程的起始函数
arg参数是传递给线程函数的参数

线程终止

单个线程可以通过以下三种方式退出,因此可以在不终止进程的情况下停止控制流:
(1). 线程可以简单的从启动例程中返回,返回值是线程的退出码
(2). 线程可以被同意进程的其他线程取消。
(3). 线程调用pthread_exit;
与进程的exit一样线程退出时与调用pthread_exit

#include <pthread.h>
void pthread_exit(void *ptr);//ptr是一个无类型指针,进程可用pthread_join访问这个指针
int pthread_jion(pthread_t pid, void **ptr);

调用pthread_jion的线程将一直阻塞,直到指定线程调用pthread_exit,从启动例程返回或者被取消。若果是从启动例程返回,ptr就包含返回码。如果线程被取消,由ptr指定内存单元就设置为PTHREAD_CANCELED。可以用pthread_join自动把线程置于分离状态,这样资源就可以恢复,如果对线程的返回值不感兴趣,就可以把ptr设置为NULL,调用ptrhead_join函数就可以等待指定线程退出,但并不获得线程的退出码。
在pthread_exit和pthread_join之间传递参数不只是线程的状态,还可以是其他自己定义的数据结构的地址,但是这个结构所使用的内存在调用者完成调用后必须是有效的。如果线程在栈上给改结构分配了内存,其他线程在访问时内存上的数据已经被改变,又或者在线程退出时,把这个结构传给pthread_exit,那么调用pthread_join的线程要使用改结构时,这个栈可能已经被 撤销,这块内存可能已经另作他用了。
在多线程中,线程可以调用pthrread_cancel函数来请求同一进程中的其他线程

#include <pthread.h>
int pthread_cancel(pthread_t pid);//成功返回0,失败返回错误编号

在默认情况下,pthread_cancel函数会使用由ptid标识的线程的行为表现为如同调用了PTHREAD_CANCELED的pthread_exit函数,但是线程可以选择忽略取消或者控制如何被取消。而且pthread_cancel并不等待进程退出,它仅仅提出请求。

在默认情况下,线程的终止状态会保存到对该线程调用pthread_join。如果线程已被分离,线程底层的存储资源可以在线程结束时立即被收回。在线程被分离后,不能用pthread_join函数等待它终止,对分离线程调用pthread_join会产生未定义行为。可调用pthread_detach分离线程。

#include <pthread.h>
int pthread_detach(pthread_t tid);
//成功返回0,失败返回错误码

线程同步

线程同步即同一时间只允许一个线程访问某个变量,以保证数据的完整性。下面就是保证线程同步的几种种方式。

互斥量

互斥量(mutex)就是一把锁,在访问共享资源前对互斥量加锁,在访问完后再对互斥量解锁释放该互斥量。在互斥量被加锁后,所有试图对该互斥量加锁的线程都将被阻塞,直到当前线程释放改互斥量。每次都只有一个线程能得到该互斥量。
互斥变量是用pthread_mutex_t表示的,在使用前必须初始化,可以把它设置为常亮PTHREAD_MUTEX_INITIALIZER(只适用于静态分配的互斥量 ),也可以通过调用pthread_mutex_init函数进行初始化。如果是动态分配的互斥量在释放内存前要调用pthread_mutex_destroy销毁

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex ,
                    const pthread_mutexattr *attr);
int ptrhead_mutex_destroy(pthread_mutex_t *mutex);

对互斥量加锁需要调用pthread_mutex_lock,解锁调用pthread_mutex_unlock

#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
//成功返回0,师表返回错误编号

如果不希望线程被阻塞,就调用pthread_mutex_trylock进行加锁,如果互斥量处于无锁状态,那么将锁住互斥量,否则加锁失败,但是不会阻塞,返回EBUSY。
在线程对一个己经加锁的互斥量加锁时会进入阻塞状态,函数pthread_mutex_timedlock允许绑定线程阻塞时间,与pthread_mutex_lock是等价的,只是增加了等待时间,在超过超时时间是,此函数不会继续等待对互斥量加锁,而是返回错误码ETIMEDOUT

#include <pthread.h>
#include <time.h>
int pthread_mutex_timedlock(pthread_mutex_t *mutex,const struct timespec *ptr);
//成功返回0,失败返回错误编号

读写锁

    读写锁和互斥量类似,不过读写锁允许更高的并行性。读写锁可以有三种状态:读模式下加锁,写模式下加锁,不加锁状态。一次只有一个线程可以占有写模式锁,但是多个线程可以同时占有读模式的读写锁。
    当读写锁是写加锁状态时,在这个锁之前,所有试图对这个锁加锁的线程都会被阻塞。挡读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是任何希望以写模式对其进行加锁的线程都会被阻塞,直到所有线程释放它们的读锁为止。
    读写锁适用于对数据结构读的次数远大于写的情况。读写锁也叫共享互斥锁,在读模式下是共享锁,在写模式下是互斥锁。
    读写锁的初始化和销毁
#include<pthread.d>
int pthread_rwlock_init(pthread_rwlock_t *rwlock,const pthread_rwlockattr_t *attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
//成功返回0,失败返回错误编号

读写锁的加锁和解锁

#include <pthread.h>
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
//成功返回0,失败返回错误编码

与互斥量一样,读写锁也有带超时的读写锁

#include <pthread.h>
#include <time.h>
int pthread_rwlock_timedrdlock(pthread_rwlock_t *rwlock,const struc timesecp *ptr);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *rwlock,const struc timesecp *ptr);
//成功返回0,失败返回错误码

条件变量

条件变量是线程可用的另一种同步机制,条件变量给多个下车提供了一个回合的场合。条件变量与互斥量一起使用时,允许线程一无竞争的方式等待特定的条件发生。
条件变量使用前要初始化,释放内存空间前要销毁

#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cond,const pthread_condattr_t * attr );
int ptrhead_cond_destroy(pthread_cond_t *cond);
//成功返回0,失败返回错误码

使用pthread_cond_wait等待条件变量为真。如果给定时间内条件不能满足会生成一个返回错误码的变量。

 #include <pthread.h>
 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 *ptr);
 //成功返回0,失败返回错误码

传递给pthread_cond_walt的互斥量对条件变量进行保护。调用者把锁住的互斥量传递给函数,函数然后自动把调用线程放到等待条件的线程列表上,对互斥量解锁。这就关闭了条件检查和线程进入休眠状态等待条件改变这两个操作之间的通道,这样线程就不会错过条件的任何变化。
有两个函数用于通知线程条件已经满足。

#include<pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
//成功返回0,失败返回错误编号

自旋锁

自旋锁与互斥量类似,但它不是通过休眠使进程阻塞,而是在获取锁之前一直处于忙等阻塞状态,不停查看互斥量的状态。在自旋锁等待时,CPU是不能做其他事,导致CPU的浪费。

#include<pthread.h>
int pthread_spin_init(pthread_spinlock_t *lock,int pshared);
int pthread_spin_destroy(pthread_spinlock_t *lock);

//使用
int pthread_spin_lock(pthread_spinlock_t *lock);
int pthread_spin_trylock(pthread_spinlock_t *lock);
int pthread_spin_unlock(pthread_spinlock_t *lock);
//成功返回0,失败返回错误码

屏障

屏障是多用户协调多线程工作的同步机制。屏障允许每个线程等待,直到所有合作的线程都达到某一点,然后从该点执行。pthread_join就是一种屏障,允许线程等待直到一个线程退出。

#include <pthread.h>
int pthread_barrier_init(pthread_barrier_t * barrier,const pthread_barrierattr *attr, unsiged int count);
int pthread_barrier_destroy(pthread_barrier_t *barrier);

int pthread_barrier_wait(pthread_barrier_t *barrier);
//成功返回0,失败返回错误码
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值