线程同步: 多线程环境中,无论调度顺序怎么样,都能得到我们想要的结果
同步的方法: 信号量、互斥锁、条件变量、读写锁
互斥锁只能用于互斥型场景,它的作用等同于二值(0/1)信号量的作用
pthread_create
:创建并启动线程
pthread_exit
:退出线程
#include
#include
#include
#include
#include
// 线程函数的返回值和参数都为void*
void* fun(void* arg){
for(int i = 0; i < 5; i++){
printf("fun run\n");
sleep(1);
}
}
int main(){
pthread_t id;
// 线程id,线程属性,线程执行函数,函数参数 成功返回0
int res = pthread_create(&id, NULL, fun, NULL);
if(res != 0){
printf("create thread error!\n");
exit(0);
}
for(int i = 0; i < 2; i++){
printf("main run\n");
sleep(1);
}
// exit(0); //退出进程
pthread_exit(NULL); // 退出线程
}
一般而言,主线程退出并不影响子线程,只是因为main函数结束时会自动调用exit
,导致整个进程退出,所有线程都结束。
pthread_join
:等待线程结束
#include
#include
#include
#include
#include
// 线程函数的返回值和参数都为void*
void* fun(void* arg){
for(int i = 0; i < 5; i++){
printf("fun run\n");
sleep(1);
}
pthread_exit("fun over!\n");
}
int main(){
pthread_t id;
// 线程id,线程属性,线程执行函数,函数参数 成功返回0
int res = pthread_create(&id, NULL, fun, NULL);
if(res != 0){
printf("create thread error!\n");
exit(0);
}
for(int i = 0; i < 2; i++){
printf("main run\n");
sleep(1);
}
// exit(0); //退出进程
// pthread_exit(NULL); // 退出线程
char* info = NULL;
pthread_join(id, (void**)&info); // 等待id线程结束
printf("%s\n", info);
exit(0);
}
分析打印结果
#include
#include
#include
#include
#include
// 线程函数的返回值和参数都为void*
void* fun(void* arg){
int index = *(int*)arg;
for(int i = 0; i < 3; i++){
printf("index = %d\n", index);
sleep(1);
}
}
int main(){
pthread_t id[5];
// 线程id,线程属性,线程执行函数,函数参数 成功返回0
for(int i = 0; i < 5; i++){
pthread_create(&id[i], NULL, fun, &i);
}
for(int i = 0; i < 5; i++){
// 不接收返回值,传NULL
pthread_join(id[i], NULL);
}
exit(0);
}
程序中,主线程创建了5个子线程,把变量i
的地址传给fun
,然而当子线程去从变量i
的地址中获取值
的时候,此时的值很可能已经被主线程改变了(改变后的值一定比创建线程时的值大),已经不是创建线程时传入的那个值了。
打印结果 ≤ 5000 \leq5000 ≤5000
#include
#include
#include
#include
#include
int g_val = 0;
void* fun(void* arg){
for(int i = 0; i < 1000; i++){
g_val++;
}
}
int main(){
pthread_t id[5];
// 线程id,线程属性,线程执行函数,函数参数 成功返回0
for(int i = 0; i < 5; i++){
pthread_create(&id[i], NULL, fun, NULL);
}
for(int i = 0; i < 5; i++){
// 不接收返回值,传NULL
pthread_join(id[i], NULL);
}
printf("g_val = %d\n", g_val);
exit(0);
}
在多核处理器的机器上可能出现:一个线程读取数据进行加1,还没来得及写回,另外一个线程也读取了并且提前写回,这就导致最后的结果肯定小于5000
非线程安全函数strtok()
#include
#include
#include
#include
#include
// 线程函数的返回值和参数都为void*
void* fun(void* arg){
char arr[] = {"1 2 3 4 5 6"};
char* s = strtok(arr, " ");
while(s != NULL){
printf("fun s = %s\n", s);
sleep(1);
s = strtok(NULL, " ");
}
}
int main(){
pthread_t id;
pthread_create(&id, NULL, fun, NULL);
char str[] = {"a b c d e f"};
char* p = strtok(str, " ");
while(p != NULL){
printf("main p = %s\n", p);
sleep(1);
p = strtok(NULL, " ");
}
pthread_join(id, NULL);
exit(0);
}
运行结果:
main p = a
fun s = 1
fun s = 2
main p = 3
main p = 4
fun s = 5
fun s = 6
main p = 6
出现问题: 出现字母后全是数字
非线程安全的函数是由什么原因导致?
函数内使用了全局变量或静态变量,导致函数strtok内部记录的全局变量被一个线程修改后,指向的地址就改变了,比如后面一直指向变量arr
#include
#include
#include
#include
#include
// 线程函数的返回值和参数都为void*
void* fun(void* arg){
char arr[] = {"1 2 3 4 5 6"};
char* ptr = NULL; // 记录上次分割到哪了
char* s = strtok_r(arr, " ", &ptr);
while(s != NULL){
printf("fun s = %s\n", s);
sleep(1);
s = strtok_r(NULL, " ", &ptr);
}
}
int main(){
pthread_t id;
pthread_create(&id, NULL, fun, NULL);
char str[] = {"a b c d e f"};
char* ptr = NULL;
char* p = strtok_r(str, " ", &ptr);
while(p != NULL){
printf("main p = %s\n", p);
sleep(1);
p = strtok_r(NULL, " ", &ptr);
}
pthread_join(id, NULL);
exit(0);
}
运行结果:
main p = a
fun s = 1
main p = b
fun s = 2
fun s = 3
main p = c
fun s = 4
main p = d
fun s = 5
main p = e
main p = f
fun s = 6
多进程和多线程混用
#include
#include
#include
#include
#include
void* fun(void* arg){
for(int i = 0; i < 50; i++){
printf("fun run pid = %d\n", getpid());
sleep(1);
}
}
int main(){
pthread_t id;
pthread_create(&id, NULL, fun, NULL);
fork();
for(int i = 0; i < 50; i++){
printf("main run pid = %d\n", getpid());
sleep(1);
}
exit(0);
}
ps -eLf
:查看进程和线程的信息
子进程拷贝的是父进程所有的资源(信号量、锁的状态一开始都是和父进程相同),但是子进程在哪拷贝就在哪开始执行,不会拷贝父进程拥有的线程。一句话总结就是:子进程产生的时候,该进程内部只有一个线程 。
如果一定要在多线程环境下使用多进程,一定要在没有线程使用锁的时候进行fork 。否则fork的时候不清楚锁的状态,子进程无法正常使用。
注意: 不能不管三七二十一,子进程上来就执行unlock,避免被lock某些资源随意被线程抢占。
解决办法: 使用pthread_atfork
,fork前进行加锁,fork完成后父子进程都unlock
void prepare(void){
pthread_mutex_lock(&mutex);
}
void parent(void){
pthread_mutex_unlock(&mutex);
}
void child(void){
pthread_mutex_unlock(&mutex);
}
pthread_atfork(prepare, parent, child);
进程pid
进程pid(进程ID),每个进程在系统中都有一个唯一·的非负整数表示的进程ID,用getpid() 获取进程ID。
线程tid
线程tid(线程ID),每个线程在所属进程中都有一个唯一的线程ID,用pthread_self() 获取自身现成ID。有多个进程时,可能会出现多个线程ID相同的线程,故线程tid只在其所属的进程上下文中有意义,不能作为系统中某个线程的唯一标识符。
线程pid
线程pid,每个线程在系统中都有一个唯一的pid标识符,用系统调用sys_call(SYS_gettid()) 获取自身线程pid。主线程pid与所在进程pid相同。