Linux线程

一. 线程

pthread不是linux下默认的库,有可能需要安装。

sudo apt-get install glibc-doc

sudo apt-get install manpages-posix-dev

通过man pthread_creat查看man pages中关于pthread的手册。

#include 
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
// Compile and link with -pthread.
  • 参数
    • thread,传出参数,线程id;
    • attr,线程属性,一般不用;
    • void *(*start_routine) (void *)函数指针,要执行的函数;
    • arg执行函数的参数。
  • 返回值
    • 成功返回0;
    • 失败返回errno number。

这里讲下void *是什么意思:无类型指针”,可以指向任何数据类型。

  1. 任何类型的指针变量都可以赋值给void *;
  2. void *赋值给其他变量需要强制类型转换。
int *p1 = nullptrvoid *p2;
p2 = p1;
p1 = (int *)p2;

Ⅰ. 创建线程:

#include 
#include 
#include 

void *print(void *argv) {
  printf("I am a thread, pid = %d, tid = %lu\n", getpid(), pthread_self());
  return argv;
}

int main() {
  pthread_t tid;
  pthread_create(&tid, NULL, print, NULL);
  printf("I am a main thread, pid = %d, tid = %lu\n", getpid(), pthread_self());
  sleep(1);
  return 0;
}

每次写makefile很麻烦,推荐个方法。

# 如果是bash就是~/.bashrc, 我用的是zsh, ~/.zshrc
vim ~/.zshrc
# 在最后添加这一句话,前提是你在家目录下有bin文件,bin文件下有写好的makefile文件
alias echomake="cat ~/bin/makefile >> makefile"
# alias有起别名的作用,把"cat ~/bin/makefile >> makefile"这个命令起别名为echomake
source ~/.zshrc
# 注:要是使用的是bash, 就把zshrc换成bash

这样我们每次只需要在终端输入echomake就会在当前目录生成一个编写好的模板makefile,编写makefile可以参考我的 makefile简单使用 ,希望对大家有帮助。

通过pthread_exit()退出线程。不能用exit()退出,这样会导致整个线程退出。

Ⅱ. 线程回收

阻塞等待回收。

man pthread_join

#include 
int pthread_join(pthread_t thread, void **retval);
Compile and link with -pthread.
  • 参数
    • thread,创建线程时传入的第一个参数;
    • retval,传出参数,线程返回的信息。
  • 返回值
    • 成功返回0;
    • 失败返回errno number。
#include 
#include 
#include 
#include 

#include 

void *print(void *arg) {
  printf("I am a thread, tid = %lu\n", pthread_self());
  int *ret = new int(100);
  return (void *)ret; // 这里返回的是100的地址,不是100!!!
}

int main() {
  pthread_t tid;
  pthread_create(&tid, NULL, print, NULL);
  printf("I am main thread, tid = %lu\n", pthread_self());
  void *ret = NULL;
  pthread_join(tid, &ret); // 这里的&ret是地址的地址了。
  // 这里&ret才是二级指针变量retval的地址
  // std::cout << *(int *)ret << std::endl;
  printf("ret = %d\n", *(int *)ret);
  return 0;
}

我觉得代码应该是这样子写的,还有这种写法的。

#include 
#include 
#include 
#include 

void *print(void *arg) {
  printf("I am a thread, tid = %lu\n", pthread_self());
  return (void *)100;
}

int main() {
  pthread_t tid;
  pthread_create(&tid, NULL, print, NULL);
  printf("I am main thread, tid = %lu\n", pthread_self());
  void *ret;
  pthread_join(tid, &ret);
  printf("ret = %d\n", (int)ret);
  return 0;
}

这种还是有点问题,g++编译报错,gcc编译只是警告。

首先,pthread_join()的第二个参数是一个void **类型的,我们传递void *ret 的地址进去,这时,ret就是返回的地址了,我们要取的是地址里面的值,因为要转换成整形输出,所以void *要强制转换为int *,然后取星号,得到线程传出的值,即*(int *)ret,所以第二种写法,还是欠妥当的。

但是,第一种写法,要是在栈上为ret 分配空间,会出现段错误。这个是因为,当print()函数结束时,就自动释放了栈地址空间,我们在main()函数还要访问,就会导致段错误了,因为我们访问了一段未知的地址空间。而堆空间是由程序员释放,程序员没释放就等程序运行结束系统自动释放。

第二种写法为什么用gcc编译也能通过,只是有警告信息,这里是强行将地址转化为了整数,竟然和线程返回的值是一样的,奇奇怪怪。

但是,这样子想想,第二种写法,不也是泄露的内存?函数都执行完了,不释放资源?

应该还有更好的、更优秀的写法,希望大家多多指点。

段错误产生的原因

Ⅲ. 杀死进程

man pthread_cancel

#include 
int pthread_cancel(pthread_t thread);
Compile and link with -pthread
  • 返回值
    • 成功返回0;
    • 失败返回errno number。
#include 
#include 
#include 

void *print(void *arg) {
  while (1) {
    printf("I am a thread, tid = %lu\n", pthread_self());
    sleep(1);
  }
  return NULL;
}

int main() {
  pthread_t tid;
  pthread_create(&tid, NULL, print, NULL);
  sleep(5); // 防止主线程比子线程先结束
  if (pthread_cancel(tid) == 0) {
    printf("success!\n");
  } else {
    perror("fault...");
  }
  pthread_join(tid, NULL);
  return 0;
}

线程如果是被pthread_cancel 取消的,返回值为-1,#define PTHREAD_CANCELED((void *)-1)

但是,这里就有个问题,用之前第一种方法 *(int *)ret,就会发生段错误,因为我们访问的是未知的地址空间,我也很迷惑啊~

注意,线程中必须有取消点才能杀死线程。查看所有取消点man 7 pthreads,强行设置取消点使用pthread_testcancel()函数。

Ⅳ. 线程分离

man pthread_detach

#include 
int pthread_detach(pthread_t thread);
Compile and link with -pthread.

实现线程分离不需要pthread_join()回收资源,如果用了,会出问题。

#include 
#include 
#include 
#include 

void *print(void *arg) {
  printf("I am thread, tid = %lu\n", pthread_self());
  sleep(4);
  return NULL;
}

int main() {
  pthread_t tid;
  pthread_create(&tid, NULL, print, NULL);
  printf("I am main thread, tid = %lu\n", pthread_self());
  sleep(5);
  pthread_detach(tid);
  int ret = 0;
  if ((ret = pthread_join(tid, NULL)) > 0) {
    printf("join error: %d, %s\n", ret, strerror(ret));
  }
  return 0;
}

Ⅴ. 线程属性

man pthread_attr_init初始化线程属性

man pthreaed_attr_destroy销毁线程属性

#include 
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
Compile and link with -pthread.

man pthread_attr_setdetachstate设置属性分离

#include 
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
  • 参数
    • attr,初始化的属性
    • detachstate
      • PTHREAD_CREATE_DETACHED 线程分离
      • PTHREAD_CREATE_JOINABLE 允许回收

二. 线程同步

Ⅰ. mutex互斥锁

man pthread_mutex_init

初始化锁,销毁锁。

#include 
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • 参数
    • restrict,约束该内存区域对应的数据,只能通过后面的变量进行访问和修改,mutex互斥锁
    • attr,互斥锁的属性,一般传NULL

man pthread_mutex_lock

给共享资源加锁。

如果未加锁,则给该线程加锁。

如果已经加锁,阻塞等待。

#include 
int pthread_mutex_lock(pthread_mutex_t *mutex);
  • 参数
    • mutex,init初始化的锁。
#include 
#include 
#include 
#include 

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

int sum = 0;

void *print1(void *arg) {
  while (1) {
    pthread_mutex_lock(&mutex);
    printf("1");
    sleep(rand() % 3);
    printf("2\n");
    pthread_mutex_unlock(&mutex);
    sleep(rand() % 3);
  }
}

void *print2(void *arg) {
  while (1) {
    pthread_mutex_lock(&mutex);
    printf("3");
    sleep(rand() % 3);
    printf("4\n");
    pthread_mutex_unlock(&mutex);
    sleep(rand() % 3);
  }
}

int main() {
  pthread_t tid[2];
  pthread_create(&tid[0], NULL, print1, NULL);
  pthread_create(&tid[1], NULL, print2, NULL);
  pthread_join(tid[0], NULL);
  pthread_join(tid[1], NULL);
  return 0;
}

Ⅱ. 读写锁

man pthread_rwlock_rdlock读锁

#include 
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

man pthread_rwlock_wrlock写锁

#include 
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

使用的是否声明全局变量pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER&rwlock进去即可。

Ⅲ. 信号量

man sem_init

#include 
int sem_init(sem_t *sem, int pshared, unsigned int value);
  • 参数
    • sem定义的信号量,传出参数
    • pshared
      • 0表示线程信号量
      • 非0表示进程信号量
    • value,定义信号量的个数

man sem_destroy

#include 
int sem_destroy(sem_t *sem);
  • 参数
    • sem, 初始化信号量时的第一个参数

man sem_wait

#include 
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

申请信号量,成功value–,当信号量为0时,阻塞等待

man sem_post

#include 
int sem_post(sem_t *sem);

释放信号量,value++。

信号量实现生产者消费者模型

Linux C 实现生产者消费者问题

三. 总结

多线程之间的资源共享还是比进程方便些。多线程内容还有很多,还要慢慢学习。

睡眠排序:

#include 

#include 
#include 
#include 
#include 

using namespace std;

vector<int> vec;
vector<int> ans;
int sum = 0;

template <class T>
void print(const T &array) {
  for (int num : array) {
    cout << num << " ";
  }
  cout << endl;
}

void create_numbers(int cnt) {
  for (int i = 0; i < cnt; ++i) {
    int tmp = rand() % 10 + 1;
    sum += tmp;
    vec.emplace_back(tmp);
  }
  cout << "before sort: ";
  print(vec);
}

void *func(void *arg) {
  sleep(*(int *)arg);
  ans.emplace_back(*(int *)arg);
  return NULL;
}

int main() {
  srand((unsigned int)time(nullptr));
  int cnt = 10;
  create_numbers(cnt);
  pthread_t tid[cnt];
  int i;
  for (i = 0; i < cnt; ++i) {
    int *argc = new int(vec[i]);
    pthread_create(&tid[i], NULL, func, (void *)argc);
    pthread_detach(tid[i]);
  }
  sleep(sum);
  cout << "sort after: ";
  print(ans);
  return 0;
}

你可能感兴趣的:(#,Linux,#,C/C++)