一、多线程基本概念
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个(百度) 在单核CPU单线程的处理器上,对于多线程的处理方式,只能分时切换线程,每一个线程运行一个时间片然后被换出,在这种情况下,无须担心公共临界区的变量的竞争问题,相反在对核心CPU中就需要非常严格的关注临界区的数据竞争情况。如下图所示分别为单核心多核心的线程调度情况:
二、多线程基本API介绍
1. 在Linux环境下多线程编程头文件
1 #include <errno.h> // Error code head file(EBUSY,ETIMEDOUT) 2 #include <pthread.h> // Pthread head file
2. 基本线程相关函数
1. pthread_mutex_t g_mutex; // 临界区锁定义 2. pthread_mutex_init(g_mutex,NULL); // 锁初始化函数 3. pthread_cond_t g_cond; // 触发条件定义 4. pthread_cond_init(g_cond,NULL); // 初始化条件变量 5. int ret = pthread_mutex_lock(&g_mutex); // 获取锁,获取失败则阻塞在此函数直至获取到锁,失败返回错误代码EBUSY 6. pthread_mutex_unlock(&g_mutex); // 进行解锁,释放锁 7. ret = pthread_mutex_trylock(&g_mutex); // 尝试获取锁,若获取失败,则不阻塞直接跳过,返回值为锁繁忙EBUSY 8. ret = pthread_mutex_timedlock(&g_mutex ,&outtime); // 尝试在outtime时间段之内获取锁,若超时未获取则不阻塞,并返回ETIMEDOUT 10. pthread_cond_wait(g_cond,g_mutex); // 先对锁进行解锁,并且阻塞等待cond信号触发直至触发完成 11. ret = pthread_cond_timedwait(g_cond,g_mutex,&outtime); // 先对锁进行解锁,并且等待cond信号触发,若在截止时间之前未触发,则跳出函数,并返回ETIMEDOUT 12. pthread_cond_signal(g_cond); // 单一发出触发信号,触发等待队列中的第一个线程,假设您只对队列添加了一个工作作业。那么只需要唤醒一个工作程序线程(再唤醒其它线程是不礼貌的!) 13. pthread_cond_broadcast(g_cond); // 发出广播触发信号,通知唤醒等待队列中的所有线程 14. pthread_cond_destroy(g_cond); // 销毁条件变量,归还条件变量资源 15. pthread_t th1; // 定义线程对象,类似于进程的PID号 16. pthread_create(&th1,NULL,thread_func,NULL); // Create the Thread1 & Start the thread func. 17. pthread_join(th1,NULL); // Wait the Thread1 end. 18. 待补充!
如上所示为基本的多线程编程API,上述锁的API的实现包括了多种方式,基本都能够满足常见的锁需求,对应的每一个函数调用基本上都有int类型的返回值,一般返回的内容都包括在了error.h头文件中。
其中函数pthread_cond_wait、pthread_mutex_lock会阻塞线程直至获取到对应条件,而其他锁相关函数不会一直阻塞线程,从而能够满足其他的场景。
3. 阻塞时间outtime参数设置:
\* 如下是timespec结构体的具体类型,其中都是long型 *\ struct timespec { __time_t tv_sec; /* Seconds. */ __syscall_slong_t tv_nsec; /* Nanoseconds. */ };
timespec结构体可以精确到纳秒级别,包括了两个成员变量,分别为秒tv_sec以及纳秒tv_nsec;
具体在延迟阻塞中应用是,可以先获取系统当前时间,然后在当前时间的基础上增加线程阻塞延迟增量,然后通过指针形参传递给对应的线程阻塞函数即可,详参见下:
1 #include <time.h> // 需要包含时间头文件 2 long wt_ms; // 需要延迟阻塞的时间 3 struct timespec outtime; // Defination定义 4 clock_gettime(CLOCK_REALTIME, &outtime); // Get the current time.(获取当前系统时间,注意一定要用CLOCK_REALTIME来获取系统时间) 5 6 /**************************************** 7 *** 开始设置阻塞延迟时间点,在下个时间点触发 *** 8 ****************************************/ 9 10 outtime.tv_sec += wt_ms / 1000; // 毫秒换算秒 11 time_ns = outtime.tv_nsec + (wt_ms % 1000) * 1000000; // 多出的换算成纳秒 12 if(time_ns >= 1000000000){ // 溢出当前时间则需要判断是否进位 13 outtime.tv_sec++; // 进位 14 outtime.tv_nsec = time_ns - 1000000000; // 计算进位后余 15 }else{ 16 outtime.tv_nsec = time_ns; // 无需进位直接赋值即可 17 } 18 ret = pthread_mutex_timedlock(&g_mutex ,&outtime); // 阻塞延迟一段时间,然后返回相关代码给ret.
上述代码将outtime参数阻塞延迟时间传递给了pthread_mutex_timedlock函数:
(a) 若在outtime时间节点之前获取到锁,则停止阻塞,返回ret=0;
(b) 若时间节点之后还未获取锁,则停止阻塞,返回ret=ETIMEDOUT超时。
4. 自旋锁
自旋锁与互斥锁功能一样,唯一一点不同的就是互斥量阻塞后休眠让出cpu,而自旋锁阻塞后不会让出cpu,会一直忙等待,直到得到锁!
自旋锁在用户态使用的比较少,在内核使用的比较多!自旋锁的使用场景:锁的持有时间比较短,或者说小于2次上下文切换的时间。
自旋锁在用户态的函数接口和互斥量一样,把pthread_mutex_xxx()中mutex换成spin,如:pthread_spin_init()。
5. 读写锁(非常适合于读写线程的变量共享情况)
假如现在一个线程a只是想读一个共享变量i,因为不确定是否会有线程去写他,所以我们还是要对它进行加锁。
但是这时候又一个线程b试图读共享变量i ,于是发现被锁住,那么b不得不等到a释放了锁后才能获得锁并读取 i 的值,但是两个读取操作即使是几乎同时发生也并不会像写操作那样造成竞争,因为他们不修改变量的值。
所以我们期望如果是多个线程试图读取共享变量值的话,那么他们应该可以立刻获取而不需要等待前一个线程释放因为读而加的锁。
读写锁可以很好的解决上面的问题。他提供了比互斥量跟好的并行性。因为以读模式加锁后当又有多个线程仅仅是试图再以读模式加锁时,并不会造成这些线程阻塞在等待锁的释放上。
相关读写锁的API具体如下:
1 int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); // 初始化读写锁 2 int pthread_rwlockattr_destory(pthread_rwlockattr_t *attr); // 销毁回收读写锁 3 int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); // 阻塞方式获取读锁 4 int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); // 阻塞方式获取写锁 5 int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); // 非阻塞方式获取读锁 6 int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); // 非阻塞方式获取写锁 7 int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 释放读写锁
具体使用实例代码如下:
1 #include <stdio.h> 2 #include <errno.h> // Error code head file(EBUSY) 3 #include <stdlib.h> 4 #include <unistd.h> 5 #include <pthread.h> // Pthread head file 6 7 pthread_rwlock_t rw_lock = PTHREAD_RWLOCK_INITIALIZER; 8 int global = 0; 9 10 void *thread_read_func(void *arg); 11 void *thread_write_func(void *arg); 12 13 int main(void) 14 { 15 pthread_t th1,th2; 16 printf("Mutil_Thread_Sys Starting...\n"); // Show the Starting point. 17 pthread_mutex_unlock(GMemory->g_mutex); 18 pthread_create(&th2,NULL,thread_read_func,"Read-TH"); // Create the Thread2 & Start the thread func2. 19 pthread_create(&th1,NULL,thread_write_func,"Write_TH"); // Create the Thread1 & Start the thread func1. 20 21 while(1){ // Main Processing thread. 22 ; 23 } 24 25 pthread_join(th2,NULL); // wait the Thread2 end. 26 pthread_join(th1,NULL); // Wait the Thread1 end. 27 28 printf("System Exit.\n"); // Show the Ending point. 29 return 0; 30 } 31 32 void *thread_read_func(void *arg) 33 { 34 bool ret; 35 char *pthr_name = (char *)arg; 36 while(1){ 37 ret = pthread_rwlock_rdlock(&rw_lock); 38 printf("The %s Read value:%d\n",pthr_name,global); 39 usleep(5000); 40 pthread_rwlock_unlock(&rw_lock); 41 } 42 } 43 44 void *thread_write_func(void *arg) 45 { 46 bool ret; 47 char *pthr_name = (char *)arg; 48 while(1){ 49 ret = pthread_rwlock_wrlock(&rw_lock); 50 global++; 51 usleep(5000); 52 printf("The %s Write thread value:%d\n",pthr_name,global); 53 pthread_rwlock_unlock(&rw_lock); 54 } 55 }