赵老师笔记----线程
一、线程的概念
线程是具有独立的简单资源(线程ID、寄存器、栈、信号屏蔽集、erron值,线程私有数据)的控制流。线程是CPU调度和分配的基本单位。
1.线程的特点
①同一进程内可以有多个线程
②同一进程内的不同线程,他们是共享所在进程的4GB虚拟内存空间
————————————4GB
内核
————————————3GB
栈区(先下生长)
线程1的栈区
线程2的栈区 栈区由系统自动维护,X86默认线程的栈8MB,
. ARM、POWERPC、MIPS线程栈默认2MB
.
线程n的栈区
————————————
heap (先上生长) 用户维护,malloc申请的空间
————————————
.bss
.data 全局变量,静态变量
—————————————
.rodata 只读属性的参量
.text code区,代码区
.init
——————————————0GB
③多线程数据共享容易,它关心线程间资源的互斥和同步
④用户空间的线程和进程一样,对内核来说,都是一个任务,都会统一参与OS任务调度器的调度。
2.进程和线程的区别
①线程是CPU调度和分配的基本单位,进程可以拥有至少一个线程。
②进程是OS中程序执行和资源分配的基本单位,线程的资源依附于进程。线程一般不拥有资源(除了必不可少的程序计数器,一组寄存器和栈)。
③系统开销,不同进程拥有不同空间,当切换进程时就必须切换空间,造成大的开销,线程由于共享所在进程的资源,切换线程时,无需大的开销,较容易。
④并发性,线程进程都可以实现并发性。
⑤线程不能独立存在,必须依附于进程。线程是进程中具有独立的控制流(由环境和一系列的执行指令组成)
3.多线程编程的作用
①多任务程序的设计。一个程序可能要处理不同应用,要处理多种任务,如果开发不同的进程来处理,系统开销很大,数据共享,程序结构都不方便,这时可使用多线程编程方法。
②并发程序设计。一个任务可能分成不同的步骤去完成,这些不同的步骤之间可能是松散耦合,可能通过线程的互斥,同步并发完成。这样可以为不同的任务步骤建立线程。
③网络程序设计。为提高网络的利用效率,我们可能使用多线程,对每个连接用一个线程去处理。(TCP编程,面向链接的网络编程方法,一个服务器可以服务多个客户端,主要用线程来实现。)
④数据共享。同一个进程中的不同线程共享进程的数据空间,方便不同线程间的数据共享。(额外不同线程间数据的同步与互斥)
⑤在多CPU系统中,实现真正的并行。
二、线程的操作
1.如何标识线程,用线程号标识,线程号用pthread_t数据类型来表示
2.多线程是由库提供的,编译要加库的引用参数 -lpthread(表示会引用libpthread.so)
3.对比进程和线程的操作
进程 线程
fork(vfork) pthread_create
exit(_exit) pthread_exit
wait(waitpid) pthread_join
abort pthread_cancel
getpid pthread_self
pid1==pid2 pthread_equal
atexit pthread_cleanup_push/ pthread_cleanup_pop
sigaction pthread_detach
4.创建pthread_create
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg);
功能:
创建一个线程
参数:
thread:线程标识符地址。
attr:线程属性结构体地址。
start_routine:线程函数的入口地址。
arg:传给线程函数的参数。
返回值:
成功:返回0。
失败:返回非0。
举例demo.c
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
void sub_thread_func(void *arg)
{
char *str=(char *)arg;
printf("I'm in sub thread!arg=%s\n",str);
sleep(2);
//退出子线程
pthread_exit(NULL);
}
int main()
{ //主线程
pthread_t tid;
char *string="hello,world!";
printf("Multithread demo!\n");
//创建子线程
pthread_create(&tid,NULL,(void *)sub_thread_func,(void *)string);
printf("before pthread_join!\n");
pthread_join(tid,NULL);
printf("after pthread_join!\n");
return 0;
}
编译:gcc demo.c -o demo -lpthread
注意:
①与fork不同的是pthread_create创建的线程不与父线程在同一点开始运行,而是从指定的函数开始运行,该函数运行完后,该线程也就退出了。
②线程依赖进程存在的,如果创建线程的进程结束了,线程也就结束了。
③线程函数的程序在pthread库中,故链接时要加上参数-lpthread。
5.线程终止pthread_exit
#include <pthread.h>
void pthread_exit(void *retval);
功能:
退出调用线程。
参数:
retval:存储线程退出状态的指针。
注: 一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
6.等待子线程结束pthread_join
#include <pthread.h>
int pthread_join(pthread_t thread,
void **retval);
功能:
等待子线程结束,并回收子线程资源。
参数:
thread:被等待的线程号。
retval:用来存储线程退出状态的指针的地址。
返回值:
成功返回0,失败返回非0。
注意:创建一个线程后应回收其资源,但使用pthread_join函数会使调用者阻塞,故Linux提供了线程分离函数:pthread_detach。
7.线程分离函数:pthread_detach
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能: 使调用线程与当前进程分离,使其成为一个独立的线程,该线程终止时,系统将自动回收它的资源。
参数:
thread:线程号
返回值:
成功:返回 0,失败返回非0。
注意:当调用pthread_detach以后,pthread_join函数将不再使调用者阻塞
上午练习:
用一个结构体保存学生的信息,含sex,age,name。其中线程输入学生各信息,另外的一个线程打印学生的各信息。当输入name为quit时退出。
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
typedef struct studentinfo{
char sex;
int age;
char name[50];
}STU;
int flag=0;//是否可打印
void *thread_input(void *arg)
{
STU *student = (STU *)arg;
while(1)
{
if (flag==0)
{
printf("Please input a student information:sex age name\n");
scanf("%c %d",&(student->sex),&(student->age));
fgets(student->name,49,stdin);
int i = 0;
while (student->name[i] != '\n')
i++;
student->name[i] = '\0';
flag=1;
if (!strncasecmp(student->name,"quit",strlen("quit")))
pthread_exit(NULL);//用户输入了,quit
}
}
}
void *thread_print(void *arg)
{
STU *student = (STU *)arg;
while(1)
{
if (flag==1)
{
if (!strncasecmp(student->name,"quit",strlen("quit")))
pthread_exit(NULL);//用户输入了,quit
printf("The student information is:%c %d %s\n",student->sex,student->age,student->name);
flag=0;
}
}
}
int main()
{ //主线程
STU *student;
student = malloc (sizeof(*student));
pthread_t tid1,tid2;
printf("Multithread demo!\n");
//创建子线程
pthread_create(&tid1,NULL,thread_input,(void *)student);
pthread_create(&tid2,NULL,thread_print,(void *)student);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
return 0;
}
8.判断两个线程ID是否相同pthread_equal
#include<pthread.h>
int pthread_equal(pthread_t tid1,pthread_t tid2)
如果相等返回非0值,否则返回0
if (tid2==tid1) //linux是可行的,但是在别的系统里pthread_t可能是一个结构体
9.获取自己线程的ID号pthread_self
#include<pthread.h>
int pthread_self()
返回调用该函数的线程的ID号
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
void sub_thread_func(void *arg)
{
char *str=(char *)arg;
printf("I'm in sub thread!arg=%s\n",str);
//不可在子线程中分离自己
//pthread_detach(pthread_self());
sleep(2);
printf("after sleep\n");
//退出子线程
pthread_exit(NULL);
}
int main()
{ //主线程
pthread_t tid;
char *string="hello,world!";
printf("Multithread demo!\n");
//创建子线程
pthread_create(&tid,NULL,(void *)sub_thread_func,(void *)string);
//可以在主线程中分离子线程
pthread_detach(tid);
printf("before pthread_join!\n");
pthread_join(tid,NULL);//分离后,pthread_join不再阻塞
printf("after pthread_join!\n");
// while(1);
return 0;
}
10.线程的终止方式
①线程从线程启动函数返回,返回值就是线程的退出信息
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *thead(void *arg)
{
int num = 123;
printf("after 2 seceonds, thread will return\n");
sleep(2);
return (void *)num;
}
int main(int argc, char *argv[])
{
pthread_t tid1;
int ret = 0;
int value = 0;
ret = pthread_create(&tid1, NULL, thead, NULL);
if(ret != 0)
perror("pthread_create");
pthread_join(tid1, (void *)(&value));
printf("value = %d\n", value);
return 0;
}
②线程调用pthread_exit函数
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *thread(void *arg)
{
int i = 0;
while(1)
{
printf("I am runing\n");
sleep(1);
i++;
if(i==3)
{
pthread_exit((void *)1);
}
}
return NULL;
}
int main(int argc, char *argv[])
{
int ret = 0;
pthread_t tid;
void *value = NULL;
ret = pthread_create(&tid, NULL, thread, NULL);
if(ret!=0)
perror("pthread_create");
pthread_join(tid, &value);
printf("value = %p\n", (int *)value);
return 0;
}
③线程被同一进程中的其他线程取消
运用pthread_cancel去取消线程
但要结束线程是有条件的
pthread_cancel函数的实质是发信号给目标线程thread,使目标线程退出。
此函数只是发送终止信号给目标线程,不会等待取消目标线程执行完才返回。
然而发送成功并不意味着目标线程一定就会终止,线程被取消时,线程的取消属性会决定线程能否被取消以及何时被取消。
线程的取消状态:是否可以取消
线程取消点:一定要遇到取消点才可以结束线程
线程的取消类型:可以设置取消类型,一种立即取消,一种默认属性要遇到取消点才取消
11.取消线程函数pthread_cancel
取消线程是指取消一个正在执行线程的操作。
#include <pthread.h>
int pthread_cancel(pthread_t thread);
功能:
取消线程。
参数:
thread:目标线程ID。
返回值:
成功返回 0,失败返回出错编号。
12.线程的取消状态
在Linux系统下,线程默认可以被取消。编程时可以通过pthread_setcancelstate函数设置线程是否可以被取消。
pthread_setcancelstate(int state,
int *old_state);
state:
PTHREAD_CANCEL_DISABLE:不可以被取消、
PTHREAD_CANCEL_ENABLE:可以被取消。
old_state:
保存调用线程原来的可取消状态的内存地址。
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *thread_cancel(void *arg)
{
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
int i=0;
while(1)
{
//pthread_testcancel();
sleep(1);
pthread_testcancel();
//printf("in sub thread\n");
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid1;
int ret = 0;
pthread_create(&tid1, NULL, thread_cancel, NULL);
if(ret!=0)
perror("pthread_create");
sleep(1);
pthread_cancel(tid1);
pthread_join(tid1, NULL);
return 0;
}
13.利用pthread_testcancel设置线程的取消点
线程被取消后,该线程并不是马上终止,默认情况下线程执行到消点时才能被终止。编程时可以通过pthread_testcancel函数设置线程的取消点。
void pthread_testcancel(void);
当别的线程取消调用此函数的线程时候,被取消的线程执行到此函数时结束。
POSIX.1保证线程在调用表1、表2中的任何函数时,取消点都会出现。
14.设置线程的取消类型pthread_setcanceltype
线程被取消后,该线程并不是马上终止,默认情况下线程执行到消点时才能被终止。编程时可以通过pthread_setcanceltype函数设置线程是否可以立即被取消。
pthread_setcanceltype(int type, int *oldtype);
type:
PTHREAD_CANCEL_ASYNCHRONOUS:立即取消、
PTHREAD_CANCEL_DEFERRED:不立即被取消
oldtype:
保存调用线程原来的可取消类型的内存地址。
15.线程的清理处理程序的注意事项
一个线程可建立或者叫注册多个清理函数
清理函数放在栈中,清理的执行与注册顺序相反,先注册后执行
如何注册??
16.注册清理函数pthread_cleanup_push
#include <pthread.h>
void pthread_cleanup_push(
void (* routine)(void *), void *arg);
功能:
将清除函数压栈。即注册清理函数。
参数:
routine:线程清理函数的指针。
arg:传给线程清理函数的参数。
17.删除清理函数pthread_cleanup_pop
#include <pthread.h>
void pthread_cleanup_pop(int execute);
功能:
将清除函数弹栈,即删除清理函数。
参数:
execute:线程清理函数执行标志位。
非0,弹出清理函数,执行清理函数。
0,弹出清理函数,不执行清理函数
18.何时执行清理函数
当线程执行以下动作时会调用清理函数:
①调用pthread_exit退出线程。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void cleanup(void *arg)
{
printf("clean up ptr = %s\n", (char *)arg);
free((char *)arg);
}
void *thread(void *arg)
{
char *ptr = NULL;
/*建立线程清理程序*/
printf("this is new thread\n");
ptr = (char*)malloc(100);
pthread_cleanup_push(cleanup, (void*)(ptr));
bzero(ptr, 100);
strcpy(ptr, "memory from malloc");
printf("before exit\n");
pthread_exit(NULL);
sleep(3);
/*注意push与pop必须配对使用,即使pop执行不到*/
printf("before pop\n");
pthread_cleanup_pop(1);
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread, NULL); // 创建一个线程
pthread_join(tid,NULL);
printf("process is dying\n");
return 0;
}
②响应其它线程的取消请求。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void cleanup(void *arg)
{
printf("clean up ptr = %s\n", (char *)arg);
free((char *)arg);
}
void *thread(void *arg)
{
char *ptr = NULL;
/*建立线程清理程序*/
printf("this is new thread\n");
ptr = (char*)malloc(100);
pthread_cleanup_push(cleanup, (void*)(ptr));
bzero(ptr, 100);
strcpy(ptr, "memory from malloc");
sleep(3);
/*注意push与pop必须配对使用,即使pop执行不到*/
printf("before pop\n");
pthread_cleanup_pop(1);
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread, NULL); // 创建一个线程
sleep(1);
printf("before cancel\n");
/*子线程响应pthread_cancel后,会执行线程处理函数*/
pthread_cancel(tid);
pthread_join(tid,NULL);
printf("process is dying\n");
return 0;
}
③用非零execute调用pthread_cleanup_pop。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
void cleanup_func1(void *arg)
{
printf("in cleanup func1\n");
printf("clean up ptr = %s\n", (char *)arg);
free((char *)arg);
}
void cleanup_func2(void *arg)
{
printf("in cleanup func2\n");
}
void *thread(void *arg)
{
char *ptr = NULL;
/*建立线程清理程序*/
printf("this is new thread\n");
ptr = (char*)malloc(100);
pthread_cleanup_push(cleanup_func1, (void*)(ptr));
pthread_cleanup_push(cleanup_func2, NULL);
bzero(ptr, 100);
strcpy(ptr, "memory from malloc");
/*注意push与pop必须配对使用,即使pop执行不到*/
printf("before pop\n");
pthread_cleanup_pop(1);
printf("before pop\n");
pthread_cleanup_pop(1);
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
pthread_create(&tid, NULL, thread, NULL); // 创建一个线程
pthread_join(tid,NULL);
printf("process is dying\n");
return 0;
}
无论哪种情况pthread_cleanup_pop都将删除上一次pthread_cleanup_push调用注册的清理处理函数。
注意:pthread_cleanup_pop、pthread_cleanup_push 必须配对使用。
三、GTK线程的处理
1.一般GUI应用程序默认只有一个执行线程,每次只执行一个操作,如果某个操作耗时较长,则用户界面会出现冻结的现象。所以若某个操作的时间比较长一般会创建线程去处理。
2.GTK+应用程序中创建多线程
①除了通过POSIX线程函数pthread_create()创建线程外
②实际编程时,还可以通过GTK+线程函数g_thread_create()创建一个线程。
3.需要注意的是,GTK的界面相关的代码不是线程安全函数。
因此在新线程中执行图形绘制相关的代码时,需要用函数gdk_threads_enter()和gdk_threads_leave()对GTK+代码进行包装,以保证对GTK界面的操作是互斥的。
4.gtk中使用多线程需进行相应初始化。并且编译时需要链接GTK线程库 -lgthread-2.0