文章目录
一、线程与进程
1.1.线程的介绍
典型的 UNIX/Linux 进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。进程是程序执行时的一个实例,是担当分配系统资源(CPU 时间、内存等)的基本单位。程序本身只是指令、数据及其组织形式的描述,进程才是程序(那些指令和数据)的真正运行实例。线程是操作系统能够进行运算调度的最小单位。 总结:进程------资源分配的最小单位,线程------程序执行的最小单位。
1.2.线程与进程的关系
在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器,线程相当于寄生在进程里,一个进程至少有一个线程,进程有独立的地址空间。当一个进程崩溃后,在保护模式下不会对其它进程产生影响,而在进程下的线程会随着该进程崩溃。
下面表格是线程与进程的对比:
| 操作 | 进程开销 | 线程开销 |
|---|---|---|
| 创建时间 | ~1ms | ~0.01ms |
| 上下文切换 | 较重 | 很轻 |
| 内存占用 | 独立地址空间 | 共享地址空间 |
| 通信开销 | 需要内核介入 | 直接内存访问 |
线程与进程的内存空间关系,在同一个进程的线程中,所有线程共享进程的代码段、数据段、堆、BSS 段(Block Started by Symbol专门用于存放未初始化的全局变量和静态变量),每个线程都有自己的栈区,用于存放变量和函数调用等。
二、关于多线程的 API 函数
多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,面对操作线程的 API 有创建、退出和等待操作;互斥锁是面对保护资源的操作有创建、销毁、加锁和解锁;条件变量是面对线程间交流的操作有创建、销毁、触发和广播,共 12 个函数:

2.1.线程
面对线程有创建、退出和销毁三个函数,下面是创建线程函数原型:
c
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, //定义一个pthread_t 类型的线程ID,并把它的地址传进来
const pthread_attr_t *restrict attr, //定义该线程属性,比如它的栈大小、优先级,一般写NULL设置默认
void *(*start_rtn)(void *), //编写一个这个新线程做事情的函数,并把函数名传进来
void *restrict arg); //传参数给新线程的函数里
//返回值:若成功返回0,否则返回错误编号
退出进程有多种方法,
- 线程只是从启动例程中返回,返回值是线程的退出码
- 线程可以被同一进程中的其他线程取消
- 线程调用pthread_exit:
rval_ptr 是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用 pthread_join 函数访问到这个指针。下面是该函数的原型:
c
#include <pthread.h>
int pthread_exit(void *rval_ptr); //这是线程结束时要传递出去的返回值,它可以指向任何类型的数据
//什么都不传就填NULL
线程等待,如果不调用该函数等待线程完成,主函数就早早结束进程,会导致僵尸线程的诞生,严重浪费资源,因此,当主函数完成了自己的工作,需要调用该函数等待线程的工作完成才能结束整个进程,下面是该函数的原型:
c
#include <pthread.h>
int pthread_join(pthread_t thread, //线程ID
void **rval_ptr); //需要另外定义一个变量来承接线程函数所返回的数据,void **是固定格式主要是能承接各种类型的数据
获取线程 ID 的函数:
c
#include <pthread.h>
pthread_t pthread_self(void); //返回值是线程ID
下面是各个 API 函数的综合应用,将主线程和新线程的值打印出来,该程序的输出结果可能先运行新线程再是主线程,它们的顺序是不定的,会相互抢夺资源:
c
#include <stdio.h>
#include <pthread.h>
void *t1WorkPlace(void *arg)
{
static int ret;
ret = *(int *)arg + 10;
printf("T1:This is new thread:%ld\n", (unsigned long)pthread_self());
printf("T1:arg = %d\n", *(int *)arg);
pthread_exit((void *)&ret);
}
int main()
{
pthread_t threadID;
int arg = 250;
int ret;
int *getRet = NULL;
ret = pthread_create(&threadID, NULL, t1WorkPlace, (void *)&arg);
if(ret == 0)
{
printf("Create thread success\n");
}
pthread_join(threadID, (void **)&getRet);
printf("Get value from new thread is:%d\n", *getRet);
printf("Main thread:%ld\n", pthread_self());
return 0;
}
2.2.互斥量
互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。为了防止一个全局变量被一个线程修改的过程中,被另一个线程打断,进而导致数据的错误,所以需要一把锁保护数据,这样有序的整改同一个变量,数据的准确性就提高了。下面是创建和销毁互斥量的函数:
c
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex, //需要定义一个pthread_mutex_t类型的变量为锁的ID
const pthread_mutexattr_t *restrict attr);//定义锁的属性,例如:普通锁、递归锁、检错锁等,填NULL用默认属性
int pthread_mutex_destroy(pthread_mutex_t mutex); //互斥锁的ID
//返回值:若成功返回0,否则返回错误编号
下面是加锁与解锁的函数原型:
c
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t mutex); //锁的ID
int pthread_mutex_unlock(pthread_mutex_t mutex);
//返回值:若成功返回0,否则返回错误编号
下面例子:创建两个新的线程,每个线程都有自己的工作,新的两个线程加上锁,必须等待某一个线程运行完成才轮到另一个线程,两个新线程运行的过程中可能被主线程打断,因为主线程并没有互斥锁保护:
c
#include <stdio.h>
#include <pthread.h>
pthread_mutex_t mutex;
void *t1Fun(void *arg)
{
int i;
pthread_mutex_lock(&mutex);
for(i = 0; i < 5; i++)
{
printf("T1:%ld---------------------------------------------\n", pthread_self());
printf("T1:Get data From main:%d\n", *(int *)arg);
sleep(1);
}
pthread_mutex_unlock(&mutex);
}
void *t2Fun(void *arg)
{
pthread_mutex_lock(&mutex);
printf("T2:%ld\n", pthread_self());
printf("T2:Get data From main:%d\n", *(int *)arg);
pthread_mutex_unlock(&mutex);
}
int main()
{
pthread_t t1, t2;
int ret;
int arg = 250;
pthread_mutex_init(&mutex, NULL);
ret = pthread_create(&t1, NULL, t1Fun, (void *)&arg);
ret = pthread_create(&t2, NULL, t2Fun, (void *)&arg);
if(ret == 0)
{
printf("Main:Create success\n");
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&mutex);
return 0;
}
2.3.条件变量
条件变量是线程另一可用的同步机制。条件变量给多个线程提供了一个会合的场所。条件变量与互斥量一起使用时,允许线程以无竞争的方式等待特定的条件发生。条件本身是由互斥量保护的。线程在改变条件状态前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为必须锁定互斥量以后才能计算条件。下面是创建及销毁条件变量的函数:
c
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond, //定义一个pthread_cond_t类型的条件变量
const pthread_condattr_t *restrict attr); //除非需要创建一个非默认属性的条件变量,否则pthread_cont_init函数的attr参数可以设置为NULL
int pthread_cond_destroy(pthread_cond_t cond); //填入定义的条件变量
// 返回:若成功返回0,否则返回错误编号
下面函数是等待条件的到来,只有某个线程给了合适的条件,就会进行该线程的操作:
c
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, //定义一个pthread_cond_t类型的变量
pthread_mutex_t *restrict mutex); //需要使用哪一把锁
// 返回:若成功返回0,否则返回错误编号
下面是触发条件的函数:
c
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t cond);
// 返回:若成功返回0,否则返回错误编号
创建新的两个线程,这两个线程分别对全局变量 g_data 进行修改,每当 g_data 加到 5 的时候,就触发 t1 线程的条件变量,并对 g_data 重新赋值为 0,当 t1 线程的变量 i 为 2 的时候,退出整个进程:
c
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
pthread_mutex_t mutex;
pthread_cond_t cond;
int g_data = 0;
void *t1Fun(void *arg)
{
int i = 0;
printf("t1:%ld\n", pthread_self());
while(1)
{
pthread_cond_wait(&cond, &mutex);
printf("t1:-----------------R-----U-----N-----------------------------------\n");
printf("t1:data=%d\n", g_data);
g_data = 0;
sleep(1);
i++;
printf("i = %d\n", i);
if(i == 2)
{
exit(1);
}
}
}
void *t2Fun(void *arg)
{
printf("T2:Get data From main:%d\n", *(int *)arg);
while(1)
{
pthread_mutex_lock(&mutex);
printf("t2:data:%d\n", g_data);
g_data++;
if(g_data == 5)
{
pthread_cond_signal(&cond);
}
sleep(1);
pthread_mutex_unlock(&mutex);
}
}
int main()
{
pthread_t t1, t2;
int ret;
int arg = 250;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond , NULL);
ret = pthread_create(&t1, NULL, t1Fun, (void *)&arg);
ret = pthread_create(&t2, NULL, t2Fun, (void *)&arg);
if(ret == 0)
{
printf("Main:Create success\n");
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}