文章目录
- ①大神博客先推
- ②好,现在看我的
- 线程是啥玩意儿? 非要线程不可?
- 线程与进程千丝万缕的纠缠
- 线程间资源共享情况
- ⑴共享资源
- ⑵非共享资源
- 线程的缺点
- ③线程安全问题
- 这个模块他的博客里没
- ④哔哔完了不?放码过来!
- 创建线程
- 接下来演示线程安全:
- 获取当前线程id
- 判断俩线程是否相等
- 单次初始化
- 连接(Joining)和分离(Detaching)线程
- 又到了演示线程安全的时间了
- ⑤线程属性
- ⑥敲黑板:栈管理
- 准备好小板凳
- 未完待续···
①大神博客先推
我这人就是这么的无私,这位大神应该是退隐好几年了,但是留下了不少好东西。
Posix线程详解
不过我的小白文也有不少彩蛋哦↓↓↓
②好,现在看我的
毕竟大神的博客也太长了,而且深奥,大家收藏就好了。
咱这个短,通俗易懂。
线程是啥玩意儿? 非要线程不可?
官方话就是:是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
1、提高程序的并发性
2、开销小,不需要重新分配内存
3、通信和共享数据方便
还真别说,大神就是厉害,这还有图有真相!!!
线程与进程千丝万缕的纠缠
(1)线程又被叫做轻量级进程,也有PCB,创建线程使用的底层函数和进程是一样的,都是clone。
(2)从内核里看线程和进程是一样的,都有各自不同的PCB,但是PCB指向的内存资源的三级页表是不同的。
(3)进程可以蜕变成线程,进程也可以说是主线程,就是高速路的主干道。
(4)在Linux下,线程是最小的执行单位,进程是最小的分配资源单位。
线程间资源共享情况
⑴共享资源
1、文件描述符表
2、每种信号的处理方式
3、当前工作目录
4、用户ID和组ID
5、内存地址空间
⑵非共享资源
1、线程id
2、处理器现场和栈指针
3、独立的栈空间
4、errno变量
5、信号屏蔽字
6、调度优先级
线程的缺点
1、线程不稳定(这个是真的不稳定,后面章节会提)
2、线程调试困难(这个是真的头疼,难以调试的东西)
3、线程无法使用Unix经典事件,如信号
③线程安全问题
线程安全(Thread-safeness):
线程安全:简短的说,指程序可以同时执行多个线程却不会“破坏“共享数据或者产生“竞争”条件的能力。
例如:假设你的程序创建了几个线程,每一个调用相同的库函数:
这个库函数存取/修改了一个全局结构或内存中的位置。
当每个线程调用这个函数时,可能同时去修改这个全局结构活内存位置。
如果函数没有使用同步机制去阻止数据破坏,这时,就不是线程安全的了。
这个模块他的博客里没
嘿嘿,如果看我的博客,那这就是一个彩蛋了。
我有可重入函数对于线程安全的意义(附函数表)
④哔哔完了不?放码过来!
创建线程
pthread_create
功能:创建一个线程
原语函数:
#include<pthread.h>
int pthread_create(pthread_t *thread,const pthread_tattr_t *attr,void *(*start_routine)(void *),void *arg);
参数释义:
thread:传递一个pthread_t变量进来,用以保存新线程的tid(线程id)
attr:线程属性设置,NULL代表使用默认属性(注(1))
(*start_routine)(void *):函数指针,指向新线程应该指向的函数模块
arg:老熟了,给前面那个函数传参用的,不传就写NULL
返回值:成功返回0.,失败返回错误号,错误号,错误号,前面说过errno不共享的。(线程里返回值统一这样的,后面不提了)
注(1):创建线程时,没什么特殊情况我们都是使用默认属性的,不过有时候需要做一些特殊处理,碧如调整优先级啊这些的。后面会说。
Q:怎样安全地向一个新创建的线程传递数据?
A:确保所传递的数据是线程安全的(不能被其他线程修改)。下面三个例子演示了那个应该和那个不应该。
我的代码太菜了,看那个大神的:
Example Code - Pthread Creation and Termination
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5
void *PrintHello(void *threadid)
{
int tid;
tid = (int)threadid;
printf("Hello World! It's me, thread #%d!\n", tid);
pthread_exit(NULL);
}
int main (int argc, char *argv[])
{
pthread_t threads[NUM_THREADS];
int rc, t;
for(t=0; t<NUM_THREADS; t++){
printf("In main: creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello, (void *)t);
if (rc){
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
pthread_exit(NULL);
}
接下来演示线程安全:
//下面的代码片段演示了如何向一个线程传递一个简单的整数。
//主线程为每一个线程使用一个唯一的数据结构,确保每个线程传递的参数是完整的。
int *taskids[NUM_THREADS];
for(t=0; t<NUM_THREADS; t++)
{
taskids[t] = (int *) malloc(sizeof(int));
*taskids[t] = t;
printf("Creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello,
(void *) taskids[t]);
...
}
//例子展示了用结构体向线程设置/传递参数。每个线程获得一个唯一的结构体实例。
struct thread_data{
int thread_id;
int sum;
char *message;
};
struct thread_data thread_data_array[NUM_THREADS];
void *PrintHello(void *threadarg)
{
struct thread_data *my_data;
...
my_data = (struct thread_data *) threadarg;
taskid = my_data->thread_id;
sum = my_data->sum;
hello_msg = my_data->message;
...
}
int main (int argc, char *argv[])
{
...
thread_data_array[t].thread_id = t;
thread_data_array[t].sum = sum;
thread_data_array[t].message = messages[t];
rc = pthread_create(&threads[t], NULL, PrintHello,
(void *) &thread_data_array[t]);
...
}
//例子演示了错误地传递参数。循环会在线程访问传递的参数前改变传递给线程的地址的内容。
int rc, t;
for(t=0; t<NUM_THREADS; t++)
{
printf("Creating thread %d\n", t);
rc = pthread_create(&threads[t], NULL, PrintHello,
(void *) &t);
...
}
获取当前线程id
pthread_self
功能:获取调用线程tid(注(3))
注(3):有的人就要问了,这东西不是都传出来了吗,直接打印不就完事儿了吗,为什么还要特地开一个函数去获取?
是这样的,线程id的类型是pthread_t,它在当前进程中是唯一的,但是在不同系统中这个类型有不同的实现,它可能是一个整数值,也可能是一个结构体,反正就是你猜不到的东西。
好,看原语:
#include<pthread.h>
pthread_t pthread_self(void);
判断俩线程是否相等
pthread_equal
功能:判断两个线程是否相等
原语:
#include<pthread.h>
int pthread_self(pthread_t t1,pthread_t t2);
注意这两个函数中的线程ID对象是不透明的,不是轻易能检查的。因为线程ID是不透明的对象,所以C语言的==操作符不能用于比较两个线程ID。
单次初始化
pthread_once (once_control, init_routine)
pthread_once 在一个进程中仅执行一次init_routine。
任何线程第一次调用该函数会执行给定的init_routine,不带参数,任何后续调用都没有效果。
init_routine函数一般是初始化的程序
once_control参数是一个同步结构体,需要在调用pthread_once前初始化。
例如:
pthread_once_t once_control = PTHREAD_ONCE_INIT;
连接(Joining)和分离(Detaching)线程
pthread_join(threadid,status)
pthread_detach(threadid,status)
pthread_attr_setdetachstate(attr,detachstate)
pthread_attr_getdetachstate(attr,detachstate)
连接:
“连接”是一种在线程间完成同步的方法。例如:
> pthread_join()函数阻赛调用线程直到threadid所指定的线程终止。
> 如果在目标线程中调用pthread_exit(),程序员可以在主线程中获得目标线程的终止状态。
>
> 连接线程只能用pthread_join()连接一次。若多次调用就会发生逻辑错误。
>
> 两种同步方法,互斥量(mutexes)和条件变量(condition variables),稍后讨论。
可连接(Joinable or Not)?
当一个线程被创建,它有一个属性定义了它是可连接的(joinable)还是分离的(detached)。
只有是可连接的线程才能被连接(joined),若果创建的线程是分离的,则不能连接。
POSIX标准的最终草案指定了线程必须创建成可连接的。然而,并非所有实现都遵循此约定。
使用pthread_create()的attr参数可以显式的创建可连接或分离的线程
典型四步如下:
声明一个pthread_attr_t数据类型的线程属性变量
用 pthread_attr_init()初始化改属性变量
用pthread_attr_setdetachstate()设置可分离状态属性
完了后,用pthread_attr_destroy()释放属性所占用的库资源
分离(Detaching):
pthread_detach()可以显式用于分离线程,尽管创建时是可连接的。
没有与pthread_detach()功能相反的函数
建议:
若线程需要连接,考虑创建时显式设置为可连接的。因为并非所有创建线程的实现都是将线程创建为可连接的。
若事先知道线程从不需要连接,考虑创建线程时将其设置为可分离状态。一些系统资源可能需要释放。
又到了演示线程安全的时间了
//这个例子演示了用Pthread join函数去等待线程终止。
//因为有些实现并不是默认创建线程是可连接状态,例子中显式地将其创建为可连接的。
#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 3
void *BusyWork(void *null)
{
int i;
double result=0.0;
for (i=0; i<1000000; i++)
{
result = result + (double)random();
}
printf("result = %e\n",result);
pthread_exit((void *) 0);
}
int main (int argc, char *argv[])
{
pthread_t thread[NUM_THREADS];
pthread_attr_t attr;
int rc, t;
void *status;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for(t=0; t<NUM_THREADS; t++)
{
printf("Creating thread %d\n", t);
rc = pthread_create(&thread[t], &attr, BusyWork, NULL);
if (rc)
{
printf("ERROR; return code from pthread_create()
is %d/n", rc);
exit(-1);
}
}
pthread_attr_destroy(&attr);
for(t=0; t<NUM_THREADS; t++)
{
rc = pthread_join(thread[t], &status);
if (rc)
{
printf("ERROR; return code from pthread_join() is %d\n", rc);
exit(-1);
}
printf("Completed join with thread %d status= %ld\n",t, (long)status);
}
pthread_exit(NULL);
}
⑤线程属性
linux下线程属性是可以根据实际项目需要进行设置。
之前我们讨论的都是线程的默认属性,默认属性已经可以解决大部分线程开发时的需求。
如果需要更高的性能,就需要人为对线程属性进行配置。
typedef struct
{
int detachstate; //线程的分离状态
int schedpolicy; //线程的调度策略
struct sched schedparam;//线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程栈的设置
void* stackaddr; //线程栈的启始位置
size_t stacksize; //线程栈大小
}pthread_attr_t;
//在上面我们可以看到,关于这个结构体中的相关参数
默认的属性为非绑定、非分离、缺省的堆栈、与父进程同样级别的优先级。
线程属性设置的一般套路:
第一:定义属性变量并初始化 pthread_attr_t
pthread_attr_init()
第二:调用你想设置的属性的接口函数
pthread_attr_setxxxxxxxx()
第三:创建线程的时候,第二个参数使用这个属性
第四:销毁属性
pthread_destroy();
⑥敲黑板:栈管理
防止栈问题:
POSIX标准并没有指定线程栈的大小,依赖于实现并随实现变化。
很容易超出默认的栈大小,常见结果:程序终止或者数据损坏。
安全和可移植的程序应该不依赖于默认的栈限制,但是取而代之的是用pthread_attr_setstacksize分配足够的栈大小。
pthread_attr_getstackaddr和pthread_attr_setstackaddr函数可以被程序用于将栈设置在指定的内存区域。
pthread_attr_getstacksize (attr, stacksize)
pthread_attr_setstacksize (attr, stacksize)
pthread_attr_getstackaddr (attr, stackaddr)
pthread_attr_setstackaddr (attr, stackaddr)
准备好小板凳
//这个例子演示了如何去查询和设定线程栈大小。
#include <pthread.h>
#include <stdio.h>
#define NTHREADS 4
#define N 1000
#define MEGEXTRA 1000000
pthread_attr_t attr;
void *dowork(void *threadid)
{
double A[N][N];
int i,j,tid;
size_t mystacksize;
tid = (int)threadid;
pthread_attr_getstacksize (&attr, &mystacksize);
printf("Thread %d: stack size = %li bytes /n", tid, mystacksize);
for (i=0; i<N; i++)
for (j=0; j<N; j++)
A[i][j] = ((i*j)/3.452) + (N-i);
pthread_exit(NULL);
}
int main(int argc, char *argv[])
{
pthread_t threads[NTHREADS];
size_t stacksize;
int rc, t;
pthread_attr_init(&attr);
pthread_attr_getstacksize (&attr, &stacksize);
printf("Default stack size = %li/n", stacksize);
stacksize = sizeof(double)*N*N+MEGEXTRA;
printf("Amount of stack needed per thread = %li/n",stacksize);
pthread_attr_setstacksize (&attr, stacksize);
printf("Creating threads with stack size = %li bytes/n",stacksize);
for(t=0; t<NTHREADS; t++){
rc = pthread_create(&threads[t], &attr, dowork, (void *)t);
if (rc){
printf("ERROR; return code from pthread_create() is %d/n", rc);
exit(-1);
}
}
printf("Created %d threads./n", t);
pthread_exit(NULL);
}
未完待续···
想了想,线程同步模块和线程池放在下一个章节吧,我没有文章字数破W的好习惯