线程的概念:
线程是进程内部的一条执行序列(执行流), 每个进程至少有一条执行序列: main的执行体。
进程可以通过线程库创建N条线程, 这些新创建的线程称之为函数线程,main函数所代表的线程为主线程。
线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。在串行程序基础上引入线程和进程是为了提高程序的并发度,从而提高程序运行效率和响应时间。
线程与进程的区别:
1、 进程是资源分配的最小单位, 线程是CPU调度的最小单位
2、 线程是轻量级的进程
3、 管理方式不同, 进程是PCB管理, 线程是由线程结构管理。
线程和进程在使用上各有优缺点:
线程执行开销小,但不利于资源的管理和保护;而进程正相反。同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。
线程的创建:(线程库文件的使用)pthread
int pthread_create(pthread_t *id, pthread_attr_t *attr, void* (*fun)(void*), void *arg);
创建一个函数线程!
与fork()调用创建一个进程的方法不同,pthread_create()创建的线程并不具备与主线程(即调用pthread_create()的线程)同样的执行序列,而是使其运行start_routine(arg)函数。thread返回创建的线程ID,而attr是创建线程时设置的线程属性(见下)。pthread_create()的返回值表示线程创建是否成功。尽管arg是void *类型的变量,但它同样可以作为任意类型的参数传给start_routine()函数;同时,start_routine()可以返回一个void *类型的返回值,而这个返回值也可以是其他类型,并由pthread_join()获取
包含线程创建函数的源代码要想生成可执行文件, 必须::
gcc -o pthread pthread.c-lpthread
创建出来的函数线程不同于函数调用, 函数调用是这条执行流中的一部分。
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
//#include<fcntl.h>
//#include<ctype.h>
#include<pthread.h>
#include<semaphore.h>
void*pthread_fun(void*arg)
{
char buff[128]="a b c d e f g h";
char *q=NULL;
char *p=strtok_r(buff," ",&q);
while(p!=NULL)
{
printf("fun::%s\n",p);
sleep(1);
p=strtok_r(NULL," ",&q);
}
}
void main()
{
pthread_t id;
int res=pthread_create(&id,NULL,pthread_fun,NULL);
assert(res==0);
int count=0;
char buff[128]="0 1 2 3 4 5 6 7";
char *q=NULL;
char *p=strtok_r(buff," ",&q);
while(p!=NULL)
{
printf("main::%s\n",p);
sleep(1);
p=strtok_r(NULL," ",&q);
}
}
函数线程是创建出一条独立的执行序列, 他与主线程同时执行。
线程结束: int pthread_exit(void *);参数可以设置线程结束状态
等待线程结束: int pthread_join(pthread_t id, void **);
可以获取到等待的线程通过pthread_exit设置的结束状态信息
线程函数传参:
1、 将值强转成void*
int a = (int) arg;
2、 将地址强转成void*
int a = *(int*)arg;
1、 线程同步
同步: 多进程或者多线程访问临界资源时, 必须进行同步控制。 多进程或者多线
程的执行并不完全是绝对的并行运行,有可能主线程需要等待函数线程的某些条件的发
生。
多线程之间有几个特殊的临界资源:
全局数据、 堆区数据、 文件描述符 多线程之间共用
线程间同步控制方式:
1.1信号量#include <semaphore.h>
获取:int sem_init(sem_t *sem, int shared , int value);
sem: 是一个sem_t类型指针, 指向信号量对象。
shared:是否能在多进程间共享,Linux不支持, 0
value: 信号量的初始值
返回值: 0成功-1失败
P操作:int sem_wait(sem_t *sem); //阻塞运行
V操作:int sem_post(sem_t *sem);
删除:int sem_destroy(sem_t *sem);
互斥锁
概念: 完全控制临界资源, 如果一个线程完成加锁操作, 则其他线程无论如何
都无法再完成加锁, 也就无法对临界资源进行访问。
初始化: int pthread_mutex_init(pthread_mutex_t *mutex,
pthread_mutex_attr_t *attr);
加锁:int pthread_mutex_lock(pthread_mutex_t *mutex); //阻塞运行
int pthread_mutex_trylock(pthread_mutex_t *mutex); //非阻塞版本
解锁: int pthread_mutex_unlock(pthread_mutex_t *mutex);
释放:int pthread_mutex_destroy(pthread_mutex_t *mutex);
1.3 条件变量
2、 线程安全--->可重入函数
有些库函数会使用线程间共享的数据, 如果没有同步控制, 线程操作就是不安全的,
所以, 我们使用这样一些函数时, 就必须使用其安全的版本 ---》 可重入函数
线程中 fork的使用, 锁的变化
在线程中调用 fork函数, 子进程只会启用调用fork函数的那条线程, 其他线程 不
会启用。
子进程会继承其父进程的锁以及锁的状态, 但是父子进程用的不是同一把锁, 父进
程解锁并不会影响到子进程的锁, 所以子进程有可能死锁!!!
pthread_atfork(void (*prepare)(void), void (*parent)(void ), void (*child)(void));
指定在fork调用之后, 创建子进程之前, 调用prepare函数, 获取所有的锁,
然后创建子进程, 子进程创建以后, 父进程环境中调用 parent解所有的锁, 子进
程环境中调用 child解所有的锁, 然后fork函数再返回。 这样保证了fork之后, 子进程
拿到的锁都是解锁状态, 避免死锁。
练习 :统计end前几个单词
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
//#include<ctype.h>
//#include<fcntl.h>
#include<semaphore.h>
#include<pthread.h>
char buff[128]={0};
sem_t sem;
pthread_mutex_t mutex;
void *pthread_fun(void*arg)
{
while(1)
{
if(strncmp(buff,"end",3)==0)
{
break;
}
}
printf("%d\n",strlen(buff)-1);
pthread_mutex_unlock(&mutex);
sleep(1);
}
void main()
{
// sem_init(&sem,0,0);
pthread_mutex_init(&mutex,NULL);
pthread_t id;
int res = pthread_create(&id,NULL,pthread_fun,NULL);
assert(res==0);
while(1)
{
pthread_mutex_lock(&mutex);
printf("please input:");
fflush(stdout);
// char buff[128]={0};
fgets(buff,128,stdin);
pthread_mutex_unlock(&mutex);
sleep(1);
//sem_post(&sem);
if(strncmp(buff,"end",3)==0)
{
break;
}
}
pthread_join(id,NULL);
}