Linux系统编程—线程
linux线程介绍
线程与进程的区别
-
如果典型的UNIX/Linux进程可以看成只有一个控制线程:一个进程在同一时刻只做一件事情。有了多个控制线程后,在程序设计时可以把进程设计成在同一时刻做不止一件事,每个线程各自处理独立的任务。
-
进程是程序执行时的一个实例,是担当分配系统资源(CPU时间、内存等)的基本单位。在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序的真正运行实例。
-
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
“进程——资源分配的最小单位,线程——程序执行的最小单位”
-
进程有独立的地址空间,一个进程崩溃后,不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
使用线程的理由
总的来说
进程有独立的地址空间,线程没有单独的地址空间(同一进程内的线程共享进程的地址空间)
使用多线程的理由之一是和进程相比,它是一种非常"节俭"的多任务操作方式。启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计,总的说来,一个进程的开销大约是一个线程开销的30倍左右,当然,在具体的系统上,这个数据可能会有较大的区别。
用多线程的理由之二是线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。当然,数据的共享也带来其他一些问题,有的变量不能同时被两个线程所修改,有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击,这些正是编写多线程程序时最需要注意的地方。
除了以上所说的优点外,不和进程比较,多线程程序作为一种多任务、并发的工作方式,当然有以下的优点:
- 提高应用程序响应。这对图形界面的程序尤其有意义。
- 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。
- 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序会利于理解和修改。
Linux线程开发API概要
多线程开发在 Linux 平台上已经有成熟的 pthread 库支持。其涉及的多线程开发的最基本概念主要包含三点:线程,互斥锁,条件。其中,线程操作又分线程的创建,退出,等待 3 种。互斥锁则包括 4 种操作,分别是创建,销毁,加锁和解锁。条件操作有 5 种操作:创建,销毁,触发,广播和等待。
对象 | 操作 | Linux pthread API |
---|---|---|
线程 | 创建 | pthread_create |
退出 | pthread_exit | |
等待 | pthread_join | |
互斥锁 | 创建 | pthread_mutex_init |
销毁 | pthread_mutex_destroy | |
加锁 | pthread_mutex_lock | |
解锁 | pthread_mutex_unlock | |
条件 | 创建 | |
销毁 | ||
触发 | ||
广播 | ||
等待 |
线程自身相关API
0. 函数原型
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号
int pthread_exit(void *rval_ptr);
int pthread_join(pthread_t thread, void **rval_ptr); // 返回:若成功返回0,否则返回错误编号
函数名称 | 功能 | 返回值 |
---|---|---|
pthread_create | 创建线程函数 | 若成功返回0,否则返回错误编号 |
pthread_exit | 退出线程函数 | - |
pthread_join | 线程等待函数 | 若成功返回0,否则返回错误编号 |
1. 创建线程
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void *), void *restrict arg);
// 返回:若成功返回0,否则返回错误编号
参数分析
参数 | 值/类型 |
---|---|
tidp | pthread_t 类型的指针 |
attr | 线程属性,暂设置为NULL ,即默认属性 |
start_rtn | 线程的函数指针,该函数返回值为void * ,参数为void * arg |
arg | 线程传参数据参数,类型为void * |
- 当pthread_create成功返回时,由tidp指向的内存单元被设置为新创建线程的线程ID
- 新创建的线程从start_rtn函数的地址开始运行,该函数只有一个无类型指针参数arg。如果需要向start_rtn函数传递的参数不止一个,那么需要把这些参数放到一个结构中,然后把这个结构的地址作为arg参数传入。
2. 线程退出
int pthread_exit(void *rval_ptr);
void *rval_pt
r:退出状态,为无类型的一级指针
- rval_ptr是一个无类型指针,与传给启动例程的单个参数类似。进程中的其他线程可以通过调用pthread_join函数访问到这个指针。
3. 线程等待(收回)
int pthread_join(pthread_t thread, void **rval_ptr); // 返回:若成功返回0,否则返回错误编号
参数分析
参数 | 值/类型 |
---|---|
pthread_t thread | pthread_t 类型的线程编号 |
void **rval_ptr | 收回线程退出状态 |
-
调用这个函数的线程将一直阻塞,直到指定的线程调用pthread_exit、从启动例程中返回或者被取消
-
如果对线程的返回值不感兴趣,可以把rval_ptr置为NULL。在这种情况下,调用pthread_join函数将等待指定的线程终止,但并不获得线程的终止状态。
线程创建等待及退出 demo
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
//线程传参数据结构体
struct dataBuf{
int num;
char buf[128];
};
void *func1(void *arg)
{
static int retval = 3; //记录返回状态,需为静态变量,进程推出后需保留,供main读取
printf("func1:ID = %ld \n",pthread_self()); //查看进程ID
// printf("func1:data = %s \n",(char *)arg); //查看创建线程时候携带的信息
printf("func1:dataStr num = %d \n", ((struct dataBuf *)arg) -> num);
printf("func1:dataStr dateBuf = %s \n", ((struct dataBuf *)arg) -> buf);
pthread_exit( (void *)&retval);
}
int main()
{
pthread_t t1; //线程t1
int ret; //创建返回值
int *pret; //返回状态指针
// char *data ="hello world"; //传字符串
struct dataBuf dataStr; //通过结构体封装数据
dataStr.num = 100;
strcpy(dataStr.buf, "hello world");
// ret = pthread_create(&t1, NULL, func1, (void *)data); //创建线程1
ret = pthread_create(&t1, NULL, func1, (void *)&dataStr); //创建线程1,并且通过结构体发送数据
if(ret == 0)
printf("pthread1 creat success! \n");
printf("main operation\n");
pthread_join(t1, (void **)&pret); //记录线程返回状态
printf("main: func1 quit state = %d \n",*pret );
}
线程间共享资源 demo
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int g_data = 0;
void *func1(void *arg)
{
printf("func1:data = %s \n",(char *)arg);
while(1)
{
printf("fun1: %d\n",g_data++);
sleep(1);
}
pthread_exit(NULL);
}
void *func2(void *arg)
{
printf("func2:data = %s \n",(char *)arg); //查看创建线程时候携带的信息
while(1)
{
printf("fun2: %d\n",g_data++);
sleep(1);
}
pthread_exit( NULL );
}
int main()
{
pthread_t t1;
pthread_t t2;
int ret;
char *data ="hello world"; //传字符串
ret = pthread_create(&t1, NULL, func1, (void *)data); //创建线程1
if(ret == 0)
printf("pthread1 creat success! \n");
ret = pthread_create(&t2, NULL, func2, (void*)data); //创建线程2
if(ret == 0)
printf("pthread2 creat success! \n");
while(1)
{
printf("main: %d\n",g_data++);
sleep(1);
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
}
互斥锁相关API
- 互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。
- 对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。
- 互斥变量用pthread_mutex_t数据类型表示。
1. 创建及销毁锁
函数原型
#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);
返回:若成功返回0,否则返回错误编号
参数分析
参数 | 类型/值 |
---|---|
*mutex | 互斥锁地址 |
*attr | 互斥锁属性 默认 NULL |
2. 加锁及解锁
函数原型
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t &mutex);
int pthread_mutex_unlock(pthread_mutex_t &mutex);
参数 : 互斥锁地址
返回值:若成功返回0,否则返回错误编号
3. 案例及应用
demo1: 保证线程1获得锁,且保证在线程1进行++后,data == 3时执行特定功能
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
pthread_mutex_t mutex;
int g_data = 0;
void *func1(void *arg)
{
printf("func1:data = %s \n",(char *)arg); //查看创建线程时候携带的信息
pthread_mutex_lock(&mutex); //获得锁
while(1)
{
printf("t1: %d\n",g_data++);
if(g_data == 3) //直到g_data == 3才释放锁,并且退出进程
{
printf("t1 quit============================\n");
pthread_mutex_unlock(&mutex); //释放锁
// pthread_exit(NULL); //退出线程
exit(0); //退出进程
}
}
}
void *func2(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex); //获得锁
printf("t2 :%d\n",g_data++);
pthread_mutex_unlock(&mutex); //释放锁
sleep(1);
}
}
void *func3(void *arg)
{
printf("func3:data = %s \n",(char *)arg);
printf("t2 :%d\n",g_data++);
sleep(1);
pthread_exit(NULL);
}
int main()
{
pthread_t t1;
pthread_t t2;
pthread_t t3;
char *data ="hello world"; //传字符串
int ret;
pthread_mutex_init(&mutex, NULL);
ret = pthread_create(&t1, NULL, func1, (void *)data); //创建线程1
if(ret == 0)
printf("pthread1 creat success! \n");
ret = pthread_create(&t2, NULL, func2, (void *)data); //创建线程2,不发送数据
if(ret == 0)
printf("pthread2 creat success! \n");
ret = pthread_create(&t3, NULL, func3, (void *)data); //创建线程2,不发送数据
if(ret == 0)
printf("pthread3 creat success! \n");
while(1)
{
printf("main: %d\n",g_data);
sleep(1);
}
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_join(t3, NULL);
pthread_mutex_destroy(&mutex);
}
观察运行结果分析:
线程1,线程2参与夺锁,线程3不参与夺锁,线程1,2,3均对data++,main不改变data的值。
- 当线程1先执行时,获得锁并对g_data++,当加到3释放锁并退出进程
- 当线程2先执行时,获得锁对临界资源data++后释放锁,在sleep(1)程序片刻锁被线程1获得,直到退出
- 当线程3先执行时,不争夺锁,在printf及sleep间均有可能被线程1夺得锁,直到退出
demo2: 死锁,实际应用应该尽量避免
程序中有2把锁,线程1和线程2都要去争夺这2把锁。而在程序片之间,另一把锁恰好被对方拿到时,使得每个线程想要拿的第二把所恰好在对方手上时,即谁都拿不到第二把锁时,即造成了死锁。
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
pthread_mutex_t mutex;
pthread_mutex_t mutex2;
void *func1(void *arg)
{
pthread_mutex_lock(&mutex); //获得锁1
sleep(1);
pthread_mutex_lock(&mutex2); //争夺锁2,夺不到
printf("func1:data = %s \n",(char *)arg); //查看创建线程时候携带的信息
pthread_mutex_unlock(&mutex);
pthread_mutex_unlock(&mutex2);
}
void *func2(void *arg)
{
pthread_mutex_lock(&mutex2); //获得锁2
sleep(1);
pthread_mutex_lock(&mutex); //争夺锁1,夺不到
printf("func2:ID = %ld \n",pthread_self()); //查看进程ID
printf("func2:data = %s \n",(char *)arg); //查看创建线程时候携带的信息
pthread_mutex_unlock(&mutex);
pthread_mutex_unlock(&mutex2);
}
int main()
{
pthread_t t1;
pthread_t t2;
char *data ="hello world"; //传字符串
int ret;
pthread_mutex_init(&mutex, NULL);
pthread_mutex_init(&mutex2, NULL);
ret = pthread_create(&t1, NULL, func1, (void *)data); //创建线程1
if(ret == 0)
printf("pthread1 creat success! \n");
ret = pthread_create(&t2, NULL, func2, (void *)data); //创建线程2,不发送数据
if(ret == 0)
printf("pthread2 creat success! \n");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&mutex);
pthread_mutex_destroy(&mutex2);
return 0;
}
条件变量相关AP
- 互斥量(mutex)从本质上来说是一把锁,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。
- 对互斥量进行加锁后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。
- 互斥变量用pthread_mutex_t数据类型表示。
1. 创建及销毁条件
函数原型
#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);
// 返回:若成功返回0,否则返回错误编号
返回:若成功返回0,否则返回错误编号
参数分析
参数 | 类型/值 |
---|---|
pthread_cond_t *cond | 条件地址 |
pthread_condattr_t *attr | 互斥锁属性,默认 NULL |
2. 等待条件触发
函数原型
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
// 返回:若成功返回0,否则返回错误编号
返回值:若成功返回0,否则返回错误编号
参数分析
参数 | 类型/值 |
---|---|
pthread_cond_t *cond | 条件地址 |
pthread_mutex_t *mutex | 互斥锁地址 |
pthread_cond_wait
等待触发条件变为真后执行后续程序,否则默认程序阻塞。
3. 触发
函数原型
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t &cond);
int pthread_cond_broadcast(pthread_cond_t &cond);
// 返回:若成功返回0,否则返回错误编号
参数: 条件地址
这两个函数可以用于通知线程条件已经满足。pthread_cond_signal
函数将唤醒等待该条件的某个线程,而 pthread_cond_broadcast
函数将唤醒等待该条件的所有进程。
4. 案例及应用
解析: 线程1阻塞,线程2获得锁并对共享资源data进行++,当线程2中data变为3时,触发条件信号,线程1开始执行后续程序,以此重复。
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
pthread_mutex_t mutex; //定义互斥锁
pthread_cond_t cond; //定义条件
int g_data = 0; //共享资源
void *func1(void *arg)
{
// printf("func1:data = %s \n",(char *)arg); //查看创建线程时候携带的信息
// pthread_mutex_lock(&mutex); //获得锁
while(1)
{
pthread_cond_wait(&cond, &mutex);
printf("t1 quit============================\n");
printf("t1: %d\n",g_data);
sleep(1);
g_data = 0;
}
}
void *func2(void *arg)
{
while(1)
{
pthread_mutex_lock(&mutex);
printf("t2 :%d\n",g_data++);
pthread_mutex_unlock(&mutex);
if(g_data == 3)
pthread_cond_signal(&cond);
sleep(1);
}
}
int main()
{
pthread_t t1;
pthread_t t2;
char *data ="hello world"; //传字符串
int ret;
pthread_mutex_init(&mutex, NULL); //初始化互斥锁
pthread_cond_init (&cond, NULL); //初始化条件
ret = pthread_create(&t1, NULL, func1, (void *)data); //创建线程1
if(ret != 0)
printf("pthread1 creat error! \n");
ret = pthread_create(&t2, NULL, func2, (void *)data); //创建线程2,不发送数据
if(ret != 0)
printf("pthread2 creat error! \n");
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&mutex); //删除互斥锁
pthread_cond_destroy(&cond); //删除条件
}