第十一章:线程

Posted lioker

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第十一章:线程相关的知识,希望对你有一定的参考价值。

 

多进程在代码中并不多见,因为它有很大的局限性,如分配大量资源、进程的PID个数有限等。现在更多地是使用多线程实现代码并发。

一、线程的概念

线程是一种轻量级的代码并发技术,对资源的要求较小。线程隶属于某个进程,进程内部可以使用多线程,线程内部也可以使用多线程。

线程共享进程的资源,不需要太多额外的资源,每个线程只需要额外分配一个栈区即可。

在使用多线程之后,在程序设计时就可以把进程设计成在某一时刻不止做一件事,每个线程处理各自的任务。

 

我们之前写的代码都是单线程的,也就是main()函数线程,又称主线程。主线程一旦结束,进程也就随之结束,所有线程也随之结束。

 

 

二、线程标识

和每个进程有一个进程PID一样,每个线程也有一个线程ID。进程ID不可重复,但是线程ID属于某个进程,只要在此进程中的线程ID不重复即可。

 

获取自身线程ID使用函数为pthread_self(),其函数定义如下:

#include <pthread.h>

pthread_t pthread_self(void);

Compile and link with -pthread.

函数返回值类型是pthread_t,与进程的pid_t不同,pthread_t实现可能不是整数类型(操作系统不同,类型不同)。因此我们需要使用pthread_equal()函数来对两个线程ID进行比较,其函数定义如下:

#include <pthread.h>

int pthread_equal(pthread_t t1, pthread_t t2);

Compile and link with -pthread.

该函数若t1和t2线程ID相等,返回非0数值;否则,返回0。

 

Linux和Unix都在POSIX规范中对线程有了定义,主要是一个头文件pthread.h + 一个共享库libpthread.so。所有线程相关的函数、结构体和类型一般以pthread_开头,比如接下来要讲解的线程创建函数pthread_create()。

由于使用共享库,在编译链接时,需要加上-pthread或-lpthread

 

 

三、线程创建

新增的线程可以通过pthread_create()函数创建。其函数声明如下:

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

 


函数参数以及返回值:

thread:返回的线程ID

attr:线程的属性,一般给0即可

start_rountine:线程执行函数指针

arg:线程执行函数的函数参数

返回值:成功返回0;失败返回错误码,需要使用strerror()转换为错误信息


 

示例代码如下:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <unistd.h>
 4 
 5 #define PI 3.141592
 6 
 7 void *task(void *p)
 8 {
 9     int *r = (int *)p;
10     printf("半径为%d圆面积为:%lf\\n", *r, PI*(*r)*(*r));
11 }
12 
13 int main()
14 {
15     pthread_t id;
16 
17     int x = 10;
18     pthread_create(&id, 0, &task, &x);
19     sleep(1);    // 防止主线程先结束
20 
21     return 0;
22 }

 

 

四、线程终止

如果进程中任意线程调用了exit()、_Exit()或_exit(),那么整个进程就会终止。

因此,线程需要有自己的终止方式:

1. 线程的函数执行了return或exit()。

2. 调用线程终止函数pthread_exit(void *),返回值做为终止参数

3. 线程被其它线程终止

 

pthread_exit()函数声明如下:

#include <pthread.h>

void pthread_exit(void *retval);

retval参数与传给pthread_create()参数类似。进程中的其他线程可以通过pthread_join()函数访问到这个指针,其函数声明如下:

#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);

 


函数参数以及返回值:

thread:指定的线程ID

retval:pthread_exit()函数的返回值

返回值:成功返回0;失败返回错误码,需要使用strerror()转换为错误信息


 

pthread_join()函数会导致调用线程一直阻塞,直到指定的线程调用pthread_exit()、从线程中返回或被取消。

对于取消线程,我们可以使用pthread_cancel()函数,其函数声明如下:

#include <pthread.h>

int pthread_cancel(pthread_t thread);

 

示例代码如下:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <unistd.h>
 4 
 5 void *task(void *p)
 6 {
 7     int *r = (int *)p;
 8     int area = 0;            // 只有int和void *可以互转
 9     
10     area = (*r) * (*r);
11     
12     return (void *)area;    // 不能返回局部变量的地址
13 }
14 
15 int main()
16 {
17     pthread_t id;
18 
19     int x = 10;
20     pthread_create(&id, 0, &task, &x);
21     int area;
22     pthread_join(id, (void**)&area); // 取地址即可
23     printf("长度为%d正方形面积为:%d\\n", x, area);
24 
25     return 0;
26 }

 

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

#include <pthread.h>

int pthread_detach(pthread_t thread);

 

示例代码如下:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <unistd.h>
 4 
 5 void *task(void *p)
 6 {
 7     int i;
 8     for(i = 0; i < 30; i++)
 9     {
10         printf("task:%d\\n", i);
11         usleep(10000);
12     }
13 }
14 
15 int main()
16 {
17     pthread_t id;
18     pthread_create(&id, 0, task, 0);
19     pthread_detach(id);
20 //    pthread_join(id, 0);    // 没有阻塞等待效果
21     int i;
22     for(i = 0; i < 30; i++)
23     {
24         printf("main:%d\\n", i);
25         usleep(10000);
26     }
27 
28     return 0;
29 }

执行此代码,可以发现主线程和task线程同时执行,而不是主线程阻塞等待task线程执行完成。

 

 

五、线程同步

使用线程并发并发就会导致一个问题:假设线程A对一个文件写入3000个字符“a”,而另一个线程B对这个文件写入3000个“b”,第三个线程C读取这个文件,会导致读取数据不一定是什么。

因为可能在一段时间内先执行了A;当A执行到一半CPU切换到执行B了,这时就会导致数据混乱。

 

为了保证数据一致性,我们需要保证线程同步。也就是让线程在同一时间只允许一个线程访问该变量。

 

一般采用的方式有原子操作、自旋锁、信号量和互斥体。具体细节可查看:五、并发控制

线程对应的函数是以上函数的变体(一般是在以上函数前面加上pthread_,如自旋锁初始化函数pthread_spin_lock())。

 

读写锁:

读写锁与互斥量类似,不过读写锁允许更高的并行性。

其系列函数定义如下:

#include <pthread.h>

/* 初始化读写锁 */
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
       const pthread_rwlockattr_t *restrict attr);

/* 销毁读写锁 */
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

/* 读锁 */
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);

 

条件变量:

条件变量使我们睡眠等待某种条件出现,然后线程才能继续执行,与信号(signal)类似。它必须与互斥量配合使用。

其系列函数定义如下:

#include <pthread.h>

/* 初始化条件变量 */
int pthread_cond_init(pthread_cond_t *restrict cond,
       const pthread_condattr_t *restrict attr);

/* 注销条件变量 */
int pthread_cond_destroy(pthread_cond_t *cond);

/* 唤醒条件变量 */
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);

/* 等待条件变量 */
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex,
       const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond,
       pthread_mutex_t *restrict mutex);

 

自旋锁:

其系列函数定义如下:

#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);

 

互斥量:

其系列函数定义如下:

#include <pthread.h>

/* 初始化互斥量 */
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);

/* 销毁互斥量 */
int pthread_mutex_destroy(pthread_mutex_t *mutex);

/* 上锁/解锁 */
int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_trylock( pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

 

互斥量的示例代码如下:

 1 #include <stdio.h>
 2 #include <pthread.h>
 3 #include <unistd.h>
 4 
 5 char *name[5];
 6 int index;
 7 
 8 pthread_mutex_t lock;                // 1 声明
 9 
10 void *task(void *p)
11 {
12     pthread_mutex_lock(&lock);        // 3 上锁
13     name[index] = (char*)p;
14     usleep(10000);
15     index++;
16     pthread_mutex_unlock(&lock);    // 5 解锁
17 }
18 
19 int main()
20 {
21     name[index] = "nihao";
22     index++;
23     pthread_mutex_init(&lock, 0);    // 2 初始化
24     pthread_t id1, id2;
25     pthread_create(&id1, 0, task, "hello");
26     pthread_create(&id2, 0, task, "world"); // 两个进程运行,id1刚执行,id2也开始执行,暂停,id2覆盖id1
27     pthread_join(id1, 0);
28     pthread_join(id2, 0);
29     pthread_mutex_destroy(&lock);    // 6 销毁
30     int i;
31     for(i = 0; i < index; i++)
32         printf("%s\\n", name[i]); // 空指针
33 
34     return 0;
35 }

 

以上是关于第十一章:线程的主要内容,如果未能解决你的问题,请参考以下文章

第十一章:Python の 网络编程基础

构建之法阅读笔记08-第十一章

Python编程:从入门到实践——作业——第十一章(测试代码)

第十一章

第十一章 视频播放器开发之音频播放

构建之法第十一章读后感